From 0ae3bce0507f9c490d4770b06a38279688cd7dd5 Mon Sep 17 00:00:00 2001 From: christian512 Date: Tue, 23 Aug 2022 10:52:36 +0200 Subject: [PATCH 001/602] initial commit --- doc/guide/guide-super.rst | 97 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 doc/guide/guide-super.rst diff --git a/doc/guide/guide-super.rst b/doc/guide/guide-super.rst new file mode 100644 index 0000000000..65266fb4eb --- /dev/null +++ b/doc/guide/guide-super.rst @@ -0,0 +1,97 @@ +.. _steady: + +***************************************************** +Superoperators, Pauli Basis and Channel Contraction +***************************************************** + +[Christopher Granade](http://www.cgranade.com/)
+Institute for Quantum Computing + +$\newcommand{\ket}[1]{\left|#1\right\rangle}$ +$\newcommand{\bra}[1]{\left\langle#1\right|}$ +$\newcommand{\cnot}{{\scriptstyle \rm CNOT}}$ +$\newcommand{\Tr}{\operatorname{Tr}}$ + +In this guide, we will demonstrate the ``tensor_contract`` function, which contracts one or more pairs of indices of a Qobj. This functionality can be used to find rectangular superoperators that implement the partial trace channel $S(\rho) = \Tr_2(\rho)$, for instance. Using this functionality, we can quickly turn a system-environment representation of an open quantum process into a superoperator representation. + +.. _steady-representation-plotting: + +Superoperator Representations and Plotting +========================================== + + +We start off by first demonstrating plotting of superoperators, as this will be useful to us in visualizing the results of a contracted channel. + + +In particular, we will use Hinton diagrams as implemented by [``qutip.visualization.hinton``](https://qutip.org/docs/latest/apidoc/functions.html#qutip.visualization.hinton), which +show the real parts of matrix elements as squares whose size and color both correspond to the magnitude of each element. To illustrate, we first plot a few density operators. + + +>>> visualization.hinton(identity([2, 3]).unit()); # doctest: +SKIP + + +>>> visualization.hinton(Qobj([[1, 0.5], [0.5, 1]]).unit()); # doctest: +SKIP + + +We show superoperators as matrices in the *Pauli basis*, such that any Hermicity-preserving map is represented by a real-valued matrix. This is especially convienent for use with Hinton diagrams, as the plot thus carries complete information about the channel. + +As an example, conjugation by $\sigma_z$ leaves $\mathbb{1}$ and $\sigma_z$ invariant, but flips the sign of $\sigma_x$ and $\sigma_y$. This is indicated in Hinton diagrams by a negative-valued square for the sign change and a positive-valued square for a +1 sign. + + +>>> visualization.hinton(to_super(sigmaz())); # doctest: +SKIP + + +As a couple more examples, we also consider the supermatrix for a Hadamard transform and for $\sigma_z \otimes H$. + + +>>> visualization.hinton(to_super(hadamard_transform())); # doctest: +SKIP + + + +>>> visualization.hinton(to_super(tensor(sigmaz(), hadamard_transform()))); # doctest: +SKIP + +.. _steady-reduced-channels: + +Reduced Channels +================ + +As an example of tensor contraction, we now consider the map $S(\rho) = \Tr_2[\cnot (\rho \otimes \ket{0}\bra{0}) \cnot^\dagger]$. +We can think of the $\cnot$ here as a system-environment representation of an open quantum process, in which an environment register is prepared in a state $\rho_{\text{anc}}$, then a unitary acts jointly on the system of interest and environment. Finally, the environment is traced out, leaving a *channel* on the system alone. In terms of [Wood diagrams](http://arxiv.org/abs/1111.6950), this can be represented as the composition of a preparation map, evolution under the system-environment unitary, and then a measurement map. + +# TODO: add images +![](images/sprep-wood-diagram.png) + + +The two tensor wires on the left indicate where we must take a tensor contraction to obtain the measurement map. Numbering the tensor wires from 0 to 3, this corresponds to a ``tensor_contract`` argument of ``(1, 3)``. + +```python +s_meas = qt.tensor_contract(qt.to_super(qt.identity([2, 2])), (1, 3)) +s_meas +``` + +Meanwhile, the ``super_tensor`` function implements the swap on the right, such that we can quickly find the preparation map. + +```python +q = qt.tensor(qt.identity(2), qt.basis(2)) +s_prep = qt.sprepost(q, q.dag()) +s_prep +``` + +For a $\cnot$ system-environment model, the composition of these maps should give us a completely dephasing channel. The channel on both qubits is just the superunitary $\cnot$ channel: + +```python +qt.visualization.hinton(qt.to_super(cnot())); +``` + +We now complete by multiplying the superunitary $\cnot$ by the preparation channel above, then applying the partial trace channel by contracting the second and fourth index indices. As expected, this gives us a dephasing map. + +```python +qt.tensor_contract(qt.to_super(cnot()), (1, 3)) * s_prep +``` + +```python +qt.visualization.hinton(qt.tensor_contract( + qt.to_super(cnot()), (1, 3)) * s_prep + ); +``` + From 9682275a3fe1c990aeb9e27a6b4c629d6ad77b7f Mon Sep 17 00:00:00 2001 From: christian512 Date: Wed, 24 Aug 2022 08:18:33 +0200 Subject: [PATCH 002/602] superoperator guide --- doc/guide/figures/sprep-wood-diagram.png | Bin 0 -> 21553 bytes doc/guide/guide-super.rst | 31 +++++++---------------- 2 files changed, 9 insertions(+), 22 deletions(-) create mode 100644 doc/guide/figures/sprep-wood-diagram.png diff --git a/doc/guide/figures/sprep-wood-diagram.png b/doc/guide/figures/sprep-wood-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..f7c2b2c3d0bc5782f8f327769d552ea4a8a07250 GIT binary patch literal 21553 zcmd43cUV*Xwk;e8MM2<=RH+(@(nUH_6ha9|k={X(UT)sBY)1;$ervZULbZ{-0AqYf1 z3<8mTyg&uKBU~iM1pK1#R)rg10DgilJVFDnFM4QMcmtmwAw9@cZ)ErbZ(i|HH}^4e zNBH0*dvfvR8_Vi}W*#{z_r>r*^XZ?!)X62t;iy z-wCI`>(-n;boRyDoz(f80)Aw``-E1$%uAeIst)%_kFkGtcv>k`dnPK(#eil z*r>P39iqCT!mmI)ojU*YrT65F_x1H@+`}^wS?sNdNTL-sc{w3$Crkw_{uwXKZ<3kQzh#0jESC24_4R1uJ8&yuZR8x}9IX0EG$NF>WUZ`_jjx99EE436 zQ(jNeOzDR}N91Q&Ih>M`!V>VoaB;8`SoR~-z>eWK$3N(Xhr}i$aRQSyv#QXa-Y+`r zs)x75-i6M=Q;5oBZ|R%aE)Xlw^@>*E2!yE+rk0#QEkhon%l>vBOq}YN`wO2^J%rOK z>V)S*AKE5A>rZdbX^e(jW=m;3y8VuN^f|K`PY880lR0Sh=jkwT32}@Jl^eATr7|nT z$yDh&krvk#ex#LsH|iskGXb`K`eXXrHOl3`;r$i|XNzUHI=JXXRxQi?gZZ>Hi!r90 zgCN7~rMV)o^c}uzkRC;r=3&V#VWo&=8UCdc+)B7U*5+G4UqiN@ z7cJ6Lhum}4^>=_Bi|E7yt98Z*fCDk?o&n+1;eoufStT#iq|?-VGceWHJUDJ%^bdwv zn>l1Fh1oG3tzLobN!Muuhr|nS!XPrz$yQ-&!ZS+P)1gJp_9|VG0TJ5sb>70;x2r*C z%Z69$cBI6HvE*MB2aF3(@aEz zoN)>oTZk3hT_F1leR$Oy4y=t_-7i)J#27ZmJKKdC4GkoU5jP+br5E|r9Tyv-Cd@rC zx8F!PPTS%LCi>z@#!CZFa*nFf(#FQ)q`3DXf(D_1y5a0sp{D{!`OSQd5hsGsq#*BYnWS0UPk>uAw7--|(K@;(%{SHd?B4;x)MDP4M*o@Oy2+_@2i-QOboq+0$w zjH}rx?Z_mSFE!+#bpk3plTHW(pipfbU{!6WWX4g6OZNy)1CfNM8v{exDt zY}p_YBW;{jg!$xUnQKT6VjH~5zq!uzT^S4HjH2vDh))3kx0*`L;kn$gqMIl%{jlr1 z)XN(Y*fOpv-r3KP)|YBEX=$a=nZ?|JE-_=ya>jKL4&f=RvEn?RR+1B_l8p&cOTd1Z_?TOYiF|IbJUhP$ZMn$N#eKNUgaQ zvAkR1G(30xqsapVB6GnR&pZ%00`lf>(Z_3~Ip0FT>4!!2Fk!4_uH!Ohq5!p7Kxa2_ zDb%jh7x!5R8~g}#9Yat_t$sBnPOgVW zchHIVm8jR}HY)WM1}H-IGqHQx>6k)pNy(L)5KnJdGkBD3lxJV0a9~Hpc{N08^I*Zm zDLLD!Xd(=&tECgI&$(@^ve|~y#r5OpI^K%|ojYi9YGswHZGnDHT z&NgoT7dJWJ95R(KU?nai@r}X8idBu7{H46eUiOJ5ekD7+0=FaIxqgdDUv6GSBjodA7khJP177tTSora-q}m&XcNH5BL0t;|Pmfg-|B}NR)0tzL|N|G|eXP~(ByVo(w&?Oq;23Ed0Ug|D> zFUnz3c*#lHR>cJG#HVyg@zOR^*J`{EEc!$?$)_2+0S~xPZ)fPYHF*iXs1?ApFTD8% z8^|9F#}g&)o2srG+1$c>qfoY~S(-+VN|-4DxC!CoqU7X+Y;gZvk+D}Vu%&Qehv*b# zozZV*-4$q0qGTi`Ir1!U#Ur!6!@{65tbzfn@`Zb3r@77(=I|Ntu3${l zH6-^cD=3@nu*msvVlFL1FlYevJe_Yy~977y=e2xkQ2xj zBayeG%st-t;+k2!>R(}t2I*7RJM+DtWgrq4N8d=55}$V{TY*^vhr7MJb$l$&NqrZxW%FPz@g=RuX2HD>tnM`nV`ETW0{xhuc zc`-J1qua@7xzO**6Xbi$D!Z7x45jy%m-$ba_H|%EdiYaZ4bGhS4qac{DbWNE_h%P; z<8G+*AsKtd8*M|r&+DNDQ#70lCVQcksnwd+~^ZfD?o8#!cApoI0*;h-L@`@w~msW0Eml|7vfA>U8Fv=^qDkiO%0f z*gJF{z5jHrQZBHlxKXTDq|XI^G<_#F?F88*GX%5ZeqtJ(*HCK%IgKyj0maxB!fbAa ziPGr|`^FJJKDRg{Tr%t=th?jhwYF=em>Q0o%>Hzn{KVxREd*RLY$CiIOii8vk_>kT zwb@QRUVCWJt*Hf8-My|0k)xI4O4UpM@rw#>HSE8Z_@4QfJ1!n;8YPBztN}*_l(7k! z>jcOnzxQRppDWC$I^S=gmSfphF4m)VL=f&r4m?9*2VOqVeQg$GdII`qa~fUm>wBcx zerGtgPNTgFCD&e8#NCGN{5!lM%TOQkeJfcLmW_=LGh@!+d5vCn<9u!+Zdo zaSXRDFWkYx6Izd93NyP5X`>DiU%Fy=;bryXK5E`smEtGC2KWah7fK=$T7S05K#TG> zGHiT~>g)FpPH-ux#65N51-!A^bwU9u3wtIIeX4AL=YYNspuxHkbs(OL??(EQxBU!J z;gNe~r=Rb5PU?o)4L9Cng~Y-e<8oLD5|S%S!+$kmDfK{laZ=es?g!WK0T-L%8Z{`8 z_Qd?i&50YaEL}&gFcGWXy#o5`fw(eb(-N?Va70udXJc|!%>4o=NPEX$5p}PZ96k5{9(MIVm($fqvk6`pPRzOxd zma@<+)Q+g7ywHc1P+8*{dAzX}k1+QDv_{PAnA*Tw;XS(E7N3bLyHg53bz<5((n^ z$^t)BDx{6j66NwUTl=%{@HURCSl-l^rtDHA6Skr4eD}(#d&dcrp=`1{ z$h)Y`2iCqLZR>;}ct#38<4M_u8_KqohA+k+Qk+5RC839=e_4F_k?a*ATXRBHzHjdM zI^Y*R_-DtD7uNIWt`TNAi>%CYa}OmkPpFm6-h|n{#6LPZ(@5w2izITXtu-Z|NwvhD=ArAJdB8cxKL;W|8$D|!FdCktkVecM^!FK!6V+hkCiEr z*9i_EdiS?>dH8B+5U^__)bJ`4r>1i_^Co`Qv@~04`Orakd9b#&==pj##PdMQ*80+m z=70Rbn|^pq#Rx zr|f*Vkk@l{-+}kx&wn;59}Le)ms;*Ko4!~!1GB;d2LGWQC}H5_f;>0#D%s_Lcfs0& zhdH8NgIl$o*>2YmppvFyKwK&WJ-rZQjPD9hi9d93)ma|dr9fu=p3oA8(MkUfs9VcH3|zsx%`Da=TEeciHEvC6TW%6YyeK?H7|r#XvaxP39@O>$!>kN?qg0 zs{nWCtOi^*9znfCUX~!`U65cZ@d0iIQ`BQ`udgp~@PkVhX@=^IXGxhYADT>bn(oz! z#Wc*ZJC{}aSWCXO12o?9hvlB1T6VG}ooU0(Az@6*XP1K9nKry{3*NJ${;SHGx%Si~ z)@)?{K~+)Lmo@hLsJ6Y24=wY!;K8FY$=j3GIhZ`HL#d^YGch0t1m|8fy?7bU6TfW< zKU1^3xxi^LLw`)4C8aldO6Pe`0DYzNxjeULt$msMX{)zOx3p72#M!L%3Z7i{cN0`O!qT$&(b$CLNrnAl=z3^e;PP}E%MkLy(Xm^ZlAGwt^%zaf? zzM2_nFxLg0%{M8Z$dg+qR-CF}A#(#v-g-QiE@!lxfkZ~g7EFTcr;b(%)tSN26Aol& ztQy>ov-M&Sg09#A5UN~SMHF!>ybsUb*1So?j$zlIff%9h{d|wCoIDEF?_lVu36As1 zkLQZr+OCRz{gYP|EL1FRy5aZ0H(LT8n4s#<|JV=Q3w^lzZmwJ{9bblzaJ`QIEFl}O zVS83a(6&*ZS!N7lW!^cy1r0ITIS$8)U2VaZ>8_Bcuof$Q1MNQQP?fk1icL5)pP>pG z`5Td*CLOC*lN3$Tu4&Slq^C#58GuYRf*0AAIDWb2=VG&F+a?|MuMbSvn#9&6O8I*{ z?Zs3>kP@|#R~nov2>@KrCEJoPW}ULEXuO$hGY;FPr66O3MDZ;d!2QT$EB^H0s`#g_ zFRk9owTSY_c9sN)h6Zj0W(>>4k}dXXMO7P9;Z&|b^yiB`9(NpG%!pqu8&XMTuG!OU z4?Ym8p5!Fu=79WL_mm3hH}K>!-jr?)4MQY|ClQlp<7jhJhch#>IZMY-NvqqKPEdK-*3chxecsp+YdNxJAhvci zn{agec<gw$8q+jsF|w4yGnMqsSW?7l%r!B#Q^6g1ds#pKZSl(%NsrReLs!+h?Ch2|OEKZ}UN)lv4`S^qgX3 z(C9XXEn9?XoX?F-2m4z$!+rD$e3jRDW<}XsRMb}66sA>3%Xl&qI+U%Ck)d`=+>Sw~ zO12->Wa6uhb<#nT)GpdI1fDnO>wirhlQp;YHg)my^Hb1=_ZKjImdRKomatTa>HMO$u4?Gep6w{V8kbowGuE$xj$9TT0r5|BfrE;MRf_jT0O4qBf zcTP~m9r_^z-JZI=SOx!PWnayZE>cw*`-v9f8I+iNFy$hu-tJ{648jR}5grn#c173q z_ZA~8=9q&E2X2G}S$wlxE|dar=;GMHzji%L9daMFiB2QK)PnQCZ3WWyy?x92_+)IQ zyS`?-M#D-Wb%b*jrYPOU{_7Y809~&eb56#Eh_jnjNfh6y>*oPrTWWin6fA6`*Lp;O zWGe9U!8koU60@L{p3p=O*ox$eVlJ|y~{b zEe*Lqj#|O=iqqAbRf>~_uBHz$jr_jzWnX}tlI7L`6GmH7ElI7$0&Y@+Q%Y!J0lxoH zcu;YedQl8VGAaclq^d8>I<1N7e9U{f7fG{0Aa8&g@9z2lDK;-`QWKk(2J=;t z8h{VIyA_h-s{<@(Uhcup_BQ9mx3n&!Z{{qJN*9|Q54V@-HZb=)8J<)V0-*O-vai|Bo|2;=ab9FU@Qgf zTveX1koN=`_xNe)3BCjM^HcPN2`o}{f8jzG4kNLw;D z3oFe3wBkMRku+(WG|M8V`Ts_eC5f>$wDJCMZ$6t$0Bk6CN9XgCFO2BeDEtc&`fI*3 zaDp!VI^P45kMb=+vw;?V0e~ORKKEH^(k8%;_ttoKiq(lsM`HMw|qhYW1HTD4iK8ya3Ihxe9H{9T4yhL%NI?F zklOrbF6v0)^!O&KXbZQ4GbARZp$emX!!o>C?>jjj1^11AdNE09j?~SQU)=H3RftI| zEs+WhTmhNQi@n#&W7*SpEd5;K`-Y4jTFT_7yI|QzAQ-YA$uJpu#g9cNMCq*3&M zMH4Ea4?(FUW!xC%a|cyi^?v{x!-UG==+gd<3{LOZ73m|FY5?b~)&I zu@*r{Yu=9n4oU<*$7dtB^gq@_i69FGD7HD6l7O*h`|70hze)GstoRA{XYwVmdjQ7+g%V~67?;$r za3=rTiEFan_X|DdfBMd*83Ke`=&Xho4ScVL`pO)OQW_iGuGfsu-ZbPK3r zzWx{N1jJ7P1K`rZ%fZdG8%6v?0c7_Kkw7M6=n!u4e{f_9))r3dJz^*h;GN&3lXS(T zSN|K}Gy(Q4=`cRuK7u`oFC}jK(UCAgNYiE{@i9SF#p7nh|F9ts$Nxd~<=BgNewF3g z0#>G7^yaz0qKDh#bASsT;~V0{f(=wMV`V*;POnmv03 zH>1Lo$uFtfW)b%$!6wIPE4yhN1 zCdcmj%LJ(#OtXw8G9}B3t>+K#KNUiH6ee}~FK^0D({L7*4)K+$9U7EP3dC7fD16$V z4S9o2k=c|3doO-FaRATEC`{ZU`js@yR}%i(V6WC*EcErJDI5;GVMocL)5uj%`CD-P zj1$tkd=ljgh0letf&$3{nfAn~1nB;Xo_0rytJ7MXFh@z-3|BOFf+V#)6N{ zTTKY*eVYIelsB%yoVyF?e`LCGdU%6p+G3eZ`!6vM zyH|YroFYvE#VN|7(YYT=^Aoh#8JgMs8Id`)-Ory~0j-mBV}F-Pw!4PFMBEI=2o#vc za5UQR9&*XR)m07u4Hu3(#nHmU19Z<`rGTS~+%li2mi28)e#bc^&OUlPZ(1#-VmtZD zIOuCzT#}XH9>uijWLEOFH}MFvHSvmNYJqP}^U(bXlW?oQI5>o1l4jC$@~rzogQPml z9S}2c$o%heyK3maK(?%gXTcR0xmjHrca--~*&MlB*CnXx13RulQuCvT>G{cAp~4z-<)D5QyJGQ7JP#0J5o$f3FbtZFmICO2(?$Zq(o` ziNfA`xeP*I*h_GBP>&1I@tkn{J{U{hMpeeY@ZFLLTPYVTgyi^q#U;3w<@sGk{b=()5x#ZByHY{0W z3vsI3H4sm;m_wI-04#Uexr?GIue5;u-_5lptzmZ4wbCyf z3vZI)iQix1Si{Qw#zSdWTKoxTBF7nOp9CDaa6DO$Y!9ymHrB-2Z$^FX+o6|p zO4vI}to|w$7O-YPXqnGDnJQCOh(4_SD9i?Kbhbv{m$bd!oFtu$iH$?Bj9;v$AC-qH zFW+(fat&~U?h+9}f&dT)wZ=8^{o}@9VgQNJ zoD;Uah~C&!JZJzT1_|MZE;tF7918)ognIUHyj5SPkwuQjUup9)94Kn6JuNO~04mfe zaa71@22NH;Td!4;1qujsW6)y_sEQ=!V7fLcP9qmfiXcWgVJOQZVZfz^LuVD>P2d@7 zdddYYh5D&4`Pej^BMydFmD`9xt3NHih2mO2ec)@hT@%QFd|Be4^*l_O!G#Nw?Fnob z3!Z`V5~t~atmrpyP0%8yGuoPWDJ+2dLmD3!m9-wtkdu151fV#Dg@I_kh6>(9?gTz! z$ta_kW`*o67Wed()S}t0X6Cd1na1ex`93|m>G2XP3u~W`mb3k!6~MTuX3x2KfR+;l z`ZGkkMTbpUF`aOQgz8_btymMf*w^UiTm}6lM(SR-koObah?n7-PGYE0z81!P2|O5er87B&Wwyk7sAA zbPGBQv{3PvvLDgG6L@E5yhluQ@vPyo38q?#omHqby4V!KI!J<4(oom>RYSkn!EnhB zB4?>C?LCe64nD*x_sn)gEv>CN{`~xMGI($5ZSd}xh+XTShuKDX_kNC}%DHy7vI3R` zS;VZh+}*|Xl@Iu2XM!YCti9m_|eCb58u%<@4GzqvofawXzZJOa(izKRmooRHz7i{(8Qib<7-kJ!@*pnauJfzI-j4UNI~PzatK+UgJRKGS z`o<%j&MF=xYzGlF4BxGj?fSf{)sw7GVz=+&@^cy8a^4vX87{V+zhgrV@nlBEtn%aX zBg4Zjv=pM5!vpfbHPY!=F*q&1Qr3BFRr?@bifiA_UWXV!3`6_#xtr7~bX+Vx7Zi!z z4zWtLJ++yB(+v;00=Iu?nvK<-ogT@q`V*`no?s6tu1@6$GVT~k> zTA)(}2QGG*08WPEi6W{Mz0dCfU2@;0#?U(xD*0PthSnTFa$?X0sg+TLqY+7LS1GAB zobqw0rjI!&;oqfyfWG$uvRe{;ARzQ$n3H(~^M2r;t`7m5%NobM7oAolpj-r+79#j-* zn+>Uu)(U`l2rfAjmLOZ18eF|4M*h1D#c(`rSg%(2;VN2A5TKv2VVdEc^n(w{mNqK2 z6K+kfxXKv;74}`*OkRcj9ykVEhN*Ukm5j%SGV!_gMheHx#Gpe<7mqT0h3bqag!o*k zj%h~2m`Y|+>Y!BwkD1`T(vPL_IROR9&cfvnThHpiX5a~snYLw{?S?qga%(z+oE2>Q zYVhL?)8i@7C?AQz3|xidrLNSIkFxJ`A*D-A7XPVAHhWtk5T{{g#WfkY(NgS!`gOI# zGH3ibQ*Zfi#GV3?0k@8G#AqlH>z9vP!5&g}J={sMsTH6%srwOG@} z7b8z1lR83x2FZF3xuNHmC#+!G8ZaZ)nj6LFf-7EAhOW{706k&0Itr|9m-k;|Yt}ys z7Sh&Jq|#4nSc?8wq^eQ?m{FhWum8qn#SwZc6Qi}Bf{?}OqtR4JR_!dgEpBrmloD?wOTDQ~3<12730N(?m z`!a-q{0JON)&-hLkXjvBpQZT)m@4iB7XrDT&b-=Z`P%91CZ8*PQ2G8=BD$X6NeNL} zF!r3O<+sk@8-AO4m$9;cAfB;+SL7s$;tb&%LR8Z9g^!0%C6@LAFO42Ub4(Nil7#JQj6Y~L{9b&;sV3m zhsMhvi^wy`B@ekiLcmG_3Uk$f6yO^m6F0`|$Ms69%1F1HBpU>cc}yl#oOeeo zPbB(uc+H)#sm8n^AZop!upWEoxWcK(gVW`{H(nRyE59r_BCi)p(72mEx z1AyjbU9WS=%B6u1!9srOgoSon$4DD^eonu`ZsYplrkt_P!w9Y?k4LvSi(KpZPJ}%a zH${+TYkN9>8iIdumPn$}FCkm^))kg>rE(h0A-!N`Gc~*dE|f(j6+NY`<}&Z837qGvg%vQr+WUee zQ)MHWs^w%Q&JCOE+?LwFQdPV8U5%mAW$a^8t+ZF%EDi@^P&wYzo*-DcE9L2Bj#oHX z9>s5a67RiZl~aV4#iE77K7{AuWcgaIJ4L$|T`sYE+w>1g#gS&Xmc3&#H(Mm+*yV>b zu8;6;OfppQ&sSa%n?|2pSGQa@PWW7;a3IPOB;{ z$O?b)X^{(xgS_oYMQyxS!8&IIxGB!EP`uAN%3`8G`fr4pp-56Cw)kztqSzxjz7`(2 zsG`_qml@gQHsP(mIb! zDt0d1I~+898KT0qj^dqFB(`9g6GNeeqKysJYz(&54}i0bLDNR|+L)^2xdD+1CAQs4 zaUhto0PNc1^)th*5vdk?fCDp>Z2wIZG#d5zLdOws4`qX)Cu_Ak_E&`+Kf-PR%tfh8 z@dhj=L=disAG<(}vU(2W2sjLL^{3<=)9t59t^g5Rp-HXlNBA7e+2Oo+Xg3h#nT;=) zF27o#jv=2;ZHoKEw?=!MIMj0-j^59%2`)$P3wQ{9r?eJYQr{{3_`&#D&Dn&7F8wgS zsmSi1yxFcA9SoFzoH{Aw5Qb7gA3lK{L;XaD`POKT zG1WrLi@dWFAA$$a`=K~lt*OstiND-kc`N4ibH%Fzwa1LUPY-k7^sJoaStL4--|6c8i&Ds`6DP4@`qzy17YzW3>p zj$LEVU%(fLU}VV4sA@rgCoRv~;DL}eGuKm%h7UbdYRKyP{SQ6C_a6vt?~rtjRE{Ok zKBj0;c@+s2PIl$l!m$WPkmQ4FuV@{%xEPh|3Pj^}{chJ#bIZl$3%?Td^V-_72ZGnV zU0QYs#j907Ttk>QCmcrp-DSpo!|h-T|C+W0NoYeVnf$qKvW|jNMGH|oLbN_K3>_J_B?VT)LN zd@-nZJ6erFDe?Aty+iH^5DE`aakx|vx+-_CAvuA2EiMzE5Yg!0s3AZySP+5o8txu3 z2=3TJ(LZbNI7q;T-$k%mtBYqK zT6Vg=bI<=z_jUQbNpE?8mE`B%NbYy3oPK)6;r5laZC~B?XncfxbENGwDOV?@Y+E5> zIgd?eSt!N(eqvSFT-X7w1pRnir~tAB+GHnQA@afVWmiV|=j>fe5}H6L^c8Fw&mOG3 zc@pSiuL7dlZ*p^&rnh4JDuNFFBxN-ntvAUZ{7M+}?BY6qI2V_W0uMGx#HGIr{SMR3 zPZfb+d-BPI>wquo72w_Sz^N7gv!j`TU){o;5p?T^WSd`kV=Mf(9ujQ)raDhCK!!vz zoB=#QPOaIl)L(WDdTL%$?a%Cyj&o7Kq2%gU}1Y{@GP|{)zoZzE|mOtG`h2_ zXqbm8Jz7g-)`Llecfx&7b7nelYI? zT>9fi>(QU#@{pvN)7`o+AyWX1MuAxO{Md+LU?9$TQr={l!*1V=;h*#w)D?EkP!2xa zq^(Ofx^zL2!~c5W%fkwEYC_G8t5Aea02h+EYk>LFA#cNqvsx0%ikZB*A4{~)yXczQ z_bNN8_BQskerLa}fz%)W9Xptml=wZo$$vw+s7 zrVK#0W6~s@y!L-aR4wJ9S`BXcty`ZS420r`hdW4-ZToA+rS)~h5S6oVsSo*dgYqbk$d?S&LSNRd~X;B{G`yY$0l}u&R~iY$TBFN?$0S6Z+^L#rI^+) zGtoCl9M+dq-C@ID?U;|YEo$D+I2QD{vB~FQ0IPmP#B3W9&2UV3nm6vR(I!XAH8}#= z<(wrE2Au{rKrJV^`D1-{Le+&TJqf@*0eR+4l4z5;qn0-;?KzZnFjzR2Fq4Mk#f3w* zMl^W*0BNzo$92bYYKzVOJyur6Ykd`y9@+HF=aPP*nQ{`upsm;PWm+29RU{44Vlxdl z6P8lv^{xz!p3tI`jwO#E1A2Cp$G;js+gWd*Me+mnITw%%=3EVnWoxIzCaXNKav-J1 z32uC@m#l$%vGyf5Bhmgh-A%r*Lj1_eLF-^!N^6@SctyNt1z-v+G7dQ(5P*|R_6f(* zdX4-b=PX*2cf9=|t%eb9MJ@{xrwRx=fzDPon?zf~CiQ6up=7(1Ax1$K74d}>E36A* z?d%_Ukl~Bb;+_~(D_~?4hTXaUkgh;rN1(AbH5L59#>aXW7fyFqfIom zX8?Nbj+{h`lyv#~9oFn^RDqyq!PHr@3K9=g2wdQ-!%HWfItsM={1p<&+E_p-6>x84 z8~rf%26<3cP;R2&PFjDk)Vy={Him$We)RU-0E#a%iVoqtT7%2ck=RW5^LBkI7VyR! z^atLDTaRa&OknMj5YG!p{;#<>r!Wpa*MzDvnP0%r!1satmt>WIux0+)%OUrG*x8T) zw4AuRV>BaV24qVXBn%dNsap7BOV(uE2ce$Um~9^)ksz3QdsvE_SKtm zjOoXd%N6>t$;_I1fg3DkW3>A+6(OFXnw3Khbo$q+QSql!i3piAAk*v+WpzEndh(1H z#Y_;=_WOJb#f+hc)@WrjxTslrtnfp&w$SG3mDPEXXyCare)XzsLgO`CktG4R_<)to z#AeA={`;ha0xJxbuU{xzSRX2bVg`Eh`H<<@J@}z0GWU~`mbX3+JA@k6KH1E?zzC7m zXm17XpKF5KsGgeP-T5+aAip#-*EUB30bL;A3aLsS7d`=sTO3&-#2duunykd?2CN^v zkt&rsC`!uguOiU~7X_XDgJ7OmhR!bH%5Z_{G0Kxx3)HJD7V3TPCSCnmW{VPSqPP5+qVzdD#}E>ED!TLeIZVK>~sCb6igqU;SJ+a|cD9iW>|3>+A^ zL2l4~*#Pq`u+m+Ug*jJ7$WSG=fL`ReF8d{HL8tPv$$mz0%#+izX6 z`t-nA+2GLLM&v1vqs4(4mN6D6cV+;8qOd$u>lxRdu?<|S5fpqZZ3xm&AfL$AD$TD0 z)xp{a>6D*n3YmhYtCnjymT13GMJqqy$ShZYnx}LtBs%HM^+z2&wJ83Ix{hbf{dlY* zEor5vkWV-E$9&H8X=^sqy&@}Flv0+Rk41&RYxDumq+?AU(;44*XQ;8!;Kbj-o|CsN z;D;XF>vbADg6W6eS?enSK@e260oAQ;D8A(7{LTk zg%|S6-z2M&i5E_1H&S|zhZ0PiQUEF{bTlRzJ=HHfsNRCbzeANyJA`zsCi#4)WNI5bJI%uavH7A$%WKN;9Gx- z5=FKmx$Tqentw0naCDZ?4OBramq=yGPN^Qo5*0Xn>XCEpqj1NCj|xHUUy)9F=#-)cDRVj znb-XjvFz2*R6iq6G7$tp#Pv!E@B0)V$KZtX9t29OJ3A90N=@ak2;lzO(k3uho1Hj8$vSQh_q8i--q>hdD5Oa}GJ4>PSeUkSDnNq^h@hpk;+US=-5)d&6Q@U|hRf(^_XZ zHTTz43DFTZi;D#6d)dk-;tf%PyrdSyl5c!UJo~g2pi#MFl?Gn;GF37N=;WIE>Hy?q zjAI;4^@9iV_1%BB*{mq;DApR6E_s=(7JGZVbg(3L28X%|>b;Mf| zB?lA}%7fQXZv)pGNOcDwM;ZP1+ih{bG_|o8Qd85kUMvtu3l4!J$gdP!2R6R(ef`F7 z6M@?no}b6Ve8{CGs}#%|eE>P*MWd4}@{^=C0^XOhHWMjVTaVU3)P33d=`RLQS7JiTCl2Nh$N99RQ+ErUF(Ak|e;y^GJyVO`wm% zaw0)rxqGs~n{0I9tj=%ou4$4Vdsmx^r3=$Um?!pP^qprk@DJfuiOh&wGzfvf2YoL( zZj9cO`lq)P6OYAwe0!^#Ex`a`jd&Hh_wbkX9a5#!x`xaWQkYwb$&lI?BB_QG;Ry zF$&!Ks#)UFEmD{NPzfY74DsHJ#<5^b?kID~0m*%>AC^r|)_!L1Xv$M&#umM_ZK2o^sU32sCW=MI+TQP3$rq&vBh@eqPUlcC#Jt|V$7(5Kx`}u zz)~evR#zVZV@HHe83_gh@^P%vfts0_nS_5EfD`x z7(D1`Vn(@n#>%~=!>{!(ms{%rf;(vQZhCrpL3z2;r)QMS`?FET#>N${6EdW%jiwFwv#98^>Z-}C!UfWjRgzkOwS%`6iwOm zF8O|$+DjV~4_C)Mw7*N#fpB7s{>)d`)KuDkBG*$`!iw=& zRUzo?PL|EmZp&UdD=sX|DJ*;~@4c`&WTao|G{CJ9&91JlPU$!0%L;7!BFD@=m3!-f zfKH0o{OQq#)BfhqJ0EGfKfk0!Iy=wiL~zf(j}lV|dOKnp7_A<~vXxXG^fF5Pv{!)= zsIp!3n+cZPXdWz%fG>ErE3ivM8|w^&T=D^>q>lam{s0D}(c}HwDJ;MW?C~fxu3o`IobnzY!W|-9Wk&c8dpa~(f~7z31|WOXThAOL1B4$0$HJ_ zPX+Yej;=jN3g~C`l~}ac{$7avozbuS8gUrGIoQwlK_Vq%Y;P*~tWx(4qXuWAeStEvXY;I+Bswu^!*mP^uA@;t7MK@5z zEG#K`1Zex}+TFE~7GOSC1%F@RkaeRVCK~B2t?+EM0Cx)sC24c9{ifY%pR($6;UjiU zHfR+Xr(E!xaX~PE?LcL~|DGX-`DK3iLi#OQ&m0Q9_DFk&R5||rpC)=*YA0=jnTaZ`&gz8T^|v>J^w#@ZT=vc8A&)lHu4e7^!PL|W6|9^rBWQ`1gT zDy77tDpeZAl9`zqk^h*p7f7~z>PZrY=NeiEAlDJbkCG2jE-u&)A3jV7UhRsjQ|0=Q zx?nt@=Z**a+3DP>JLDr?51&J$(M-)?YWnZf)8D@5s5KpjxYJ7GYF)d@**9!v8mp=j zQfvc|xGR#%yOpsLkH24~=T`g*40Ck2Bs2tOGAc}-K496Dx!KkCjh0IT#>Xeg;ezl* zJaPXmVwif%NTspqprTIs@JB3pMZk{pwErC4RlyZf%{y>!o$_5vOH1z}r-IJ~VyX_2 z>tO502Te&QJ+Duv-)?VUb8>zboW#C;9!$Z!EP@7ru^rPSn>NOvmG^AY4UsT)GWERy z1|t{bZRi=J>7#wCPV>Dr_$VwqX<0vLmhr7uk07UEC;)H^XCM=-ryPG)OpM z=iqn>4(Xk;g;Wy zHAmLb3<14@K3*FYWa#?f5~elF$oEXxlbQM-UR@k%91c15Yp*RNLfclWq>R^Wl~>4_ zRT~@G5D3DZdpB3Bo6e?W8bwz9WF8_8#Urk}q?|=1>nRt{ZWwQsQ9$}bLo3M-)cZZ| z*^0wHum{Pd7-q&}#UP|m8}A`mtJ-agD`k_G)pK^e|Fp=40DU$4^|RH+G|$e?PQg`L z7BtmZOfL0gW+)UV7Vm=pxTQvaB14dz$9|V3WokD)bKcN2aMH-v|7i(zonuLUd}YQ{ zRvP?>g(bWP;aLC@gtntH?12T2bV_2+Jn4q^Gy2y3*nZX zk1`Pg36MvOwR7!4TC1AZq?}F6s|2U`Z}K`%wQar8lWtIpnS1Wl`pIbdXVT#Y^+(nG z<{=~sW!kctyS_tjN7YYDLd5!m*VYX?F=O<5NE6w&WBA>Td(85{597h|DZ!%1I$7^` zD{!Y6apb9@Z4okn;1H)stmSp`y6*!6XdGxNs88C zLs2SxZPs%|irONs@a4R#N`=DpQ{K!XYubmkcVk z51xm@zWs zm{amY1`Gt6kW}3TO)aXh5+9z9WY)mS?ZJ4r@qjB3Pn{#r6m3v`AQ;q+75GM*R5*8X zy;SpPjLlA+(<`iQUqX5mu}O#TaJ*+n+Lb=-k>?ajj;ip=ytTmatq-6@Yx*Pqh5Y*ddv{A z+cZRsl|;He+IXBc@vGGMbW74t5M=%Y1V8j)~xDFdtdAU{Ka?rJyVQ6>X?P(l~PkK$9Usfwu&~;%eQ0+!^Y?oq0 z@u^QqA@t=!QxRd}5=c=0h76s98*9xyjb?1!)y2GW3y+KJr|MNYbRCJW0b=-9I=QOg zWMDn*j1&(^yGXm!q}QO^MW^q$z|(^{frX@D&)@K~iT=M}(~70{8r5(XgZqRJH#lKD zTf>X4N-D>@UaBP!0!epraIGxFroBUU-KN{bw*n4?YxSh(N;Asd1?+8guDZcINtPbJDSdOQZFzL#ahq`jcj^W|Gnr+;g3-V1ElxlnI4;0e zvWF-VS}L_ons$`sW+r6f!E2>fvGBZZJdd||8_^*f%U#7NJ!OnK2~FJ!H0PH?)6AZ^ zIEVM&7*se00-Au`ieHd5V{w{qE4vdD8x* zi&_^$C*mh1ES}HFlDiSqokJ$3PVUzF$i?M;6-mBBT2*2G0iwwKeCe2-Rq;lJB2>4{ z!MQ6=+g2BStn1V{?22`avA*gWAST8_WFo?sA&{ycwbBs~Mk6Wt3jKKQ9XKu=g z+5}DQ=TRDYYx&)6fPfs!tj&qSRsYuMgGdCYt11kB8e-3St@NpLtPGDf!?8%2c?(Hq zN3MFm@?{Z2!>F1Dn@U-P#w49!QpUH_v!K_2VS`J^562;)!Mkv-{&1D+^UPG}D9!D36uom0l# z)vm=OWl9JE8l0lB6Zx|4s2laa9FgCK<@(-qB<JSZvY(4QL#0jOm;a_>nxY5d5<#p9L{5>t2(T{v#qU zD)<$Cp+vLbgm)^H7-)mh%q~D}9VZOZz`lL-#Z;yuanZQ3azy2ql?Z zB2dxe0wqNl@2S-F<_C%{y?EoZy_)zCBT1rq$=~!*1fOYHf%PO|yVH&6O$E|I7}8&8 z2b_?Nc>88|aL`C+wy|9}b+Kh;33zKALf!~)rJ-^=4R%G&Wts$PhmSh!zwAmz-Sz=BuY~oNk=(6xp zRlNY5_5p$a8K@Niz@(p-ytoY{4nf#Jlzhu&r--)0fNf!qXO@XHe-B)7FM^6RX&t(? zxtfUDOXqoA@9kygw1Gn3R1*Ih6b1>3L?Fw{a0R60<1L})U=pXNppvX}S+99fFkDj) z zu7E-g8fiR!p4Y0?Z2SpU?(e{&Q#XuLLE-thrI*R?J}UDaNS1ZTIE2{AxB&`PB<(lD d1>c>+Z>> tensor_contract(to_super(identity([2, 2])), (1, 3)) # doctest: +SKIP Meanwhile, the ``super_tensor`` function implements the swap on the right, such that we can quickly find the preparation map. -```python -q = qt.tensor(qt.identity(2), qt.basis(2)) -s_prep = qt.sprepost(q, q.dag()) -s_prep -``` +>>> q = tensor(identity(2), basis(2)) # doctest: +SKIP +>>> s_prep = sprepost(q, q.dag()) # doctest: +SKIP For a $\cnot$ system-environment model, the composition of these maps should give us a completely dephasing channel. The channel on both qubits is just the superunitary $\cnot$ channel: -```python -qt.visualization.hinton(qt.to_super(cnot())); -``` +>>> visualization.hinton(to_super(cnot())); # doctest: +SKIP We now complete by multiplying the superunitary $\cnot$ by the preparation channel above, then applying the partial trace channel by contracting the second and fourth index indices. As expected, this gives us a dephasing map. -```python -qt.tensor_contract(qt.to_super(cnot()), (1, 3)) * s_prep -``` +>>> tensor_contract(to_super(cnot()), (1, 3)) * s_prep # doctest: +SKIP +>>> visualization.hinton(tensor_contract(to_super(cnot()), (1, 3)) * s_prep); # doctest: +SKIP -```python -qt.visualization.hinton(qt.tensor_contract( - qt.to_super(cnot()), (1, 3)) * s_prep - ); -``` From 2f3ffb9d45024a6217495e0d0626a143bde8e0c8 Mon Sep 17 00:00:00 2001 From: christian512 Date: Wed, 24 Aug 2022 08:44:38 +0200 Subject: [PATCH 003/602] insert guide to toctree --- doc/guide/guide-super.rst | 6 +++--- doc/guide/guide.rst | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/guide/guide-super.rst b/doc/guide/guide-super.rst index c013fcd4a0..f01f071268 100644 --- a/doc/guide/guide-super.rst +++ b/doc/guide/guide-super.rst @@ -1,4 +1,4 @@ -.. _steady: +.. _super: ***************************************************** Superoperators, Pauli Basis and Channel Contraction @@ -14,7 +14,7 @@ $\newcommand{\Tr}{\operatorname{Tr}}$ In this guide, we will demonstrate the ``tensor_contract`` function, which contracts one or more pairs of indices of a Qobj. This functionality can be used to find rectangular superoperators that implement the partial trace channel $S(\rho) = \Tr_2(\rho)$, for instance. Using this functionality, we can quickly turn a system-environment representation of an open quantum process into a superoperator representation. -.. _steady-representation-plotting: +.. _super-representation-plotting: Superoperator Representations and Plotting ========================================== @@ -50,7 +50,7 @@ As a couple more examples, we also consider the supermatrix for a Hadamard trans >>> visualization.hinton(to_super(tensor(sigmaz(), hadamard_transform()))); # doctest: +SKIP -.. _steady-reduced-channels: +.. _super-reduced-channels: Reduced Channels ================ diff --git a/doc/guide/guide.rst b/doc/guide/guide.rst index 751e95f318..c6d9751775 100644 --- a/doc/guide/guide.rst +++ b/doc/guide/guide.rst @@ -11,6 +11,7 @@ Users Guide guide-basics.rst guide-states.rst guide-tensor.rst + guide-super.rst guide-dynamics.rst guide-heom.rst guide-steady.rst From a965c0c1facc8c297397ede30590d3f23e5981c0 Mon Sep 17 00:00:00 2001 From: christian512 Date: Wed, 24 Aug 2022 22:02:57 +0200 Subject: [PATCH 004/602] add plot environment, update links and equations --- doc/guide/guide-super.rst | 54 ++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/doc/guide/guide-super.rst b/doc/guide/guide-super.rst index f01f071268..5a11e9771f 100644 --- a/doc/guide/guide-super.rst +++ b/doc/guide/guide-super.rst @@ -3,16 +3,15 @@ ***************************************************** Superoperators, Pauli Basis and Channel Contraction ***************************************************** +written by `Christopher Granade `, Institute for Quantum Computing -[Christopher Granade](http://www.cgranade.com/)
-Institute for Quantum Computing +.. math:: + \newcommand{\ket}[1]{\left|#1\right\rangle} + \newcommand{\bra}[1]{\left\langle#1\right|} + \newcommand{\cnot}{{\scriptstyle \rm CNOT}} + \newcommand{\Tr}{\operatorname{Tr}} -$\newcommand{\ket}[1]{\left|#1\right\rangle}$ -$\newcommand{\bra}[1]{\left\langle#1\right|}$ -$\newcommand{\cnot}{{\scriptstyle \rm CNOT}}$ -$\newcommand{\Tr}{\operatorname{Tr}}$ - -In this guide, we will demonstrate the ``tensor_contract`` function, which contracts one or more pairs of indices of a Qobj. This functionality can be used to find rectangular superoperators that implement the partial trace channel $S(\rho) = \Tr_2(\rho)$, for instance. Using this functionality, we can quickly turn a system-environment representation of an open quantum process into a superoperator representation. +In this guide, we will demonstrate the :func:`tensor_contract` function, which contracts one or more pairs of indices of a Qobj. This functionality can be used to find rectangular superoperators that implement the partial trace channel :math:S(\rho) = \Tr_2(\rho)`, for instance. Using this functionality, we can quickly turn a system-environment representation of an open quantum process into a superoperator representation. .. _super-representation-plotting: @@ -23,31 +22,32 @@ Superoperator Representations and Plotting We start off by first demonstrating plotting of superoperators, as this will be useful to us in visualizing the results of a contracted channel. -In particular, we will use Hinton diagrams as implemented by [``qutip.visualization.hinton``](https://qutip.org/docs/latest/apidoc/functions.html#qutip.visualization.hinton), which +In particular, we will use Hinton diagrams as implemented by :func:`qutip.visualization.hinton`, which show the real parts of matrix elements as squares whose size and color both correspond to the magnitude of each element. To illustrate, we first plot a few density operators. +.. plot:: + :context: >>> visualization.hinton(identity([2, 3]).unit()); # doctest: +SKIP - - >>> visualization.hinton(Qobj([[1, 0.5], [0.5, 1]]).unit()); # doctest: +SKIP We show superoperators as matrices in the *Pauli basis*, such that any Hermicity-preserving map is represented by a real-valued matrix. This is especially convienent for use with Hinton diagrams, as the plot thus carries complete information about the channel. -As an example, conjugation by $\sigma_z$ leaves $\mathbb{1}$ and $\sigma_z$ invariant, but flips the sign of $\sigma_x$ and $\sigma_y$. This is indicated in Hinton diagrams by a negative-valued square for the sign change and a positive-valued square for a +1 sign. +As an example, conjugation by :math:`\sigma_z` leaves :math:`\mathbb{1}` and :math:`\sigma_z` invariant, but flips the sign of :math:`\sigma_x` and :math:`\sigma_y`. This is indicated in Hinton diagrams by a negative-valued square for the sign change and a positive-valued square for a +1 sign. +.. plot:: + :context: >>> visualization.hinton(to_super(sigmaz())); # doctest: +SKIP -As a couple more examples, we also consider the supermatrix for a Hadamard transform and for $\sigma_z \otimes H$. +As a couple more examples, we also consider the supermatrix for a Hadamard transform and for :math:`\sigma_z \otimes H`. +.. plot:: + :context: >>> visualization.hinton(to_super(hadamard_transform())); # doctest: +SKIP - - - >>> visualization.hinton(to_super(tensor(sigmaz(), hadamard_transform()))); # doctest: +SKIP .. _super-reduced-channels: @@ -55,30 +55,38 @@ As a couple more examples, we also consider the supermatrix for a Hadamard trans Reduced Channels ================ -As an example of tensor contraction, we now consider the map $S(\rho) = \Tr_2[\cnot (\rho \otimes \ket{0}\bra{0}) \cnot^\dagger]$. -We can think of the $\cnot$ here as a system-environment representation of an open quantum process, in which an environment register is prepared in a state $\rho_{\text{anc}}$, then a unitary acts jointly on the system of interest and environment. Finally, the environment is traced out, leaving a *channel* on the system alone. In terms of [Wood diagrams](http://arxiv.org/abs/1111.6950), this can be represented as the composition of a preparation map, evolution under the system-environment unitary, and then a measurement map. +As an example of tensor contraction, we now consider the map :math:`S(\rho) = \Tr_2[\cnot (\rho \otimes \ket{0}\bra{0}) \cnot^\dagger]`. +We can think of the :math:`\cnot` here as a system-environment representation of an open quantum process, in which an environment register is prepared in a state :math:`\rho_{\text{anc}}`, then a unitary acts jointly on the system of interest and environment. Finally, the environment is traced out, leaving a *channel* on the system alone. In terms of `Wood diagrams `, this can be represented as the composition of a preparation map, evolution under the system-environment unitary, and then a measurement map. .. figure:: figures/sprep-wood-diagram.png :align: center :width: 2.5in -The two tensor wires on the left indicate where we must take a tensor contraction to obtain the measurement map. Numbering the tensor wires from 0 to 3, this corresponds to a ``tensor_contract`` argument of ``(1, 3)``. +The two tensor wires on the left indicate where we must take a tensor contraction to obtain the measurement map. Numbering the tensor wires from 0 to 3, this corresponds to a :func:`tensor_contract` argument of ``(1, 3)``. + +.. plot:: + :context: >>> tensor_contract(to_super(identity([2, 2])), (1, 3)) # doctest: +SKIP -Meanwhile, the ``super_tensor`` function implements the swap on the right, such that we can quickly find the preparation map. +Meanwhile, the :func:`super_tensor` function implements the swap on the right, such that we can quickly find the preparation map. >>> q = tensor(identity(2), basis(2)) # doctest: +SKIP >>> s_prep = sprepost(q, q.dag()) # doctest: +SKIP -For a $\cnot$ system-environment model, the composition of these maps should give us a completely dephasing channel. The channel on both qubits is just the superunitary $\cnot$ channel: +For a :math:`\cnot` system-environment model, the composition of these maps should give us a completely dephasing channel. The channel on both qubits is just the superunitary :math:`\cnot` channel: + +.. plot:: + :context: >>> visualization.hinton(to_super(cnot())); # doctest: +SKIP -We now complete by multiplying the superunitary $\cnot$ by the preparation channel above, then applying the partial trace channel by contracting the second and fourth index indices. As expected, this gives us a dephasing map. +We now complete by multiplying the superunitary :math:`\cnot` by the preparation channel above, then applying the partial trace channel by contracting the second and fourth index indices. As expected, this gives us a dephasing map. + +.. plot:: + :context: ->>> tensor_contract(to_super(cnot()), (1, 3)) * s_prep # doctest: +SKIP >>> visualization.hinton(tensor_contract(to_super(cnot()), (1, 3)) * s_prep); # doctest: +SKIP From 62da2bb292f0897c05b1de71b38b1889bf85ab76 Mon Sep 17 00:00:00 2001 From: christian512 Date: Wed, 24 Aug 2022 22:22:10 +0200 Subject: [PATCH 005/602] remove redefinitions --- doc/guide/guide-super.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/guide/guide-super.rst b/doc/guide/guide-super.rst index 5a11e9771f..70700f7b72 100644 --- a/doc/guide/guide-super.rst +++ b/doc/guide/guide-super.rst @@ -6,8 +6,6 @@ Superoperators, Pauli Basis and Channel Contraction written by `Christopher Granade `, Institute for Quantum Computing .. math:: - \newcommand{\ket}[1]{\left|#1\right\rangle} - \newcommand{\bra}[1]{\left\langle#1\right|} \newcommand{\cnot}{{\scriptstyle \rm CNOT}} \newcommand{\Tr}{\operatorname{Tr}} From 90bf7b567eb0ee6136969421d9f7f25a4eee3542 Mon Sep 17 00:00:00 2001 From: christian512 Date: Wed, 24 Aug 2022 23:42:25 +0200 Subject: [PATCH 006/602] remove Tr definition --- doc/guide/guide-super.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/guide/guide-super.rst b/doc/guide/guide-super.rst index 70700f7b72..bedc31c837 100644 --- a/doc/guide/guide-super.rst +++ b/doc/guide/guide-super.rst @@ -7,7 +7,6 @@ written by `Christopher Granade `, Institute for Quantu .. math:: \newcommand{\cnot}{{\scriptstyle \rm CNOT}} - \newcommand{\Tr}{\operatorname{Tr}} In this guide, we will demonstrate the :func:`tensor_contract` function, which contracts one or more pairs of indices of a Qobj. This functionality can be used to find rectangular superoperators that implement the partial trace channel :math:S(\rho) = \Tr_2(\rho)`, for instance. Using this functionality, we can quickly turn a system-environment representation of an open quantum process into a superoperator representation. From 89764a1eb282f8c4ad790f18e29cc67d6f5d429d Mon Sep 17 00:00:00 2001 From: christian512 Date: Thu, 25 Aug 2022 08:16:52 +0200 Subject: [PATCH 007/602] fix math section --- doc/guide/guide-super.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/guide/guide-super.rst b/doc/guide/guide-super.rst index bedc31c837..5e0d288ea9 100644 --- a/doc/guide/guide-super.rst +++ b/doc/guide/guide-super.rst @@ -52,7 +52,11 @@ As a couple more examples, we also consider the supermatrix for a Hadamard trans Reduced Channels ================ -As an example of tensor contraction, we now consider the map :math:`S(\rho) = \Tr_2[\cnot (\rho \otimes \ket{0}\bra{0}) \cnot^\dagger]`. +As an example of tensor contraction, we now consider the map + +.. math:: + S(\rho)=\Tr_2[\cnot (\rho\otimes \ket{0}\bra{0}) \cnot^\dagger] + We can think of the :math:`\cnot` here as a system-environment representation of an open quantum process, in which an environment register is prepared in a state :math:`\rho_{\text{anc}}`, then a unitary acts jointly on the system of interest and environment. Finally, the environment is traced out, leaving a *channel* on the system alone. In terms of `Wood diagrams `, this can be represented as the composition of a preparation map, evolution under the system-environment unitary, and then a measurement map. .. figure:: figures/sprep-wood-diagram.png From 1ded22f6ba931089a588cadef01bad2e3e5be2d2 Mon Sep 17 00:00:00 2001 From: christian512 Date: Thu, 25 Aug 2022 09:04:40 +0200 Subject: [PATCH 008/602] replace cnot definition --- doc/guide/guide-super.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/guide/guide-super.rst b/doc/guide/guide-super.rst index 5e0d288ea9..1886488e13 100644 --- a/doc/guide/guide-super.rst +++ b/doc/guide/guide-super.rst @@ -5,8 +5,6 @@ Superoperators, Pauli Basis and Channel Contraction ***************************************************** written by `Christopher Granade `, Institute for Quantum Computing -.. math:: - \newcommand{\cnot}{{\scriptstyle \rm CNOT}} In this guide, we will demonstrate the :func:`tensor_contract` function, which contracts one or more pairs of indices of a Qobj. This functionality can be used to find rectangular superoperators that implement the partial trace channel :math:S(\rho) = \Tr_2(\rho)`, for instance. Using this functionality, we can quickly turn a system-environment representation of an open quantum process into a superoperator representation. @@ -55,9 +53,10 @@ Reduced Channels As an example of tensor contraction, we now consider the map .. math:: - S(\rho)=\Tr_2[\cnot (\rho\otimes \ket{0}\bra{0}) \cnot^\dagger] -We can think of the :math:`\cnot` here as a system-environment representation of an open quantum process, in which an environment register is prepared in a state :math:`\rho_{\text{anc}}`, then a unitary acts jointly on the system of interest and environment. Finally, the environment is traced out, leaving a *channel* on the system alone. In terms of `Wood diagrams `, this can be represented as the composition of a preparation map, evolution under the system-environment unitary, and then a measurement map. + S(\rho)=\Tr_2 (\scriptstyle \rm CNOT (\rho \otimes \ket{0}\bra{0}) \scriptstyle \rm CNOT^\dagger) + +We can think of the :math:`\scriptstyle \rm CNOT` here as a system-environment representation of an open quantum process, in which an environment register is prepared in a state :math:`\rho_{\text{anc}}`, then a unitary acts jointly on the system of interest and environment. Finally, the environment is traced out, leaving a *channel* on the system alone. In terms of `Wood diagrams `, this can be represented as the composition of a preparation map, evolution under the system-environment unitary, and then a measurement map. .. figure:: figures/sprep-wood-diagram.png :align: center @@ -76,14 +75,14 @@ Meanwhile, the :func:`super_tensor` function implements the swap on the right, s >>> q = tensor(identity(2), basis(2)) # doctest: +SKIP >>> s_prep = sprepost(q, q.dag()) # doctest: +SKIP -For a :math:`\cnot` system-environment model, the composition of these maps should give us a completely dephasing channel. The channel on both qubits is just the superunitary :math:`\cnot` channel: +For a :math:`\scriptstyle \rm CNOT` system-environment model, the composition of these maps should give us a completely dephasing channel. The channel on both qubits is just the superunitary :math:`\scriptstyle \rm CNOT` channel: .. plot:: :context: >>> visualization.hinton(to_super(cnot())); # doctest: +SKIP -We now complete by multiplying the superunitary :math:`\cnot` by the preparation channel above, then applying the partial trace channel by contracting the second and fourth index indices. As expected, this gives us a dephasing map. +We now complete by multiplying the superunitary :math:`\scriptstyle \rm CNOT` by the preparation channel above, then applying the partial trace channel by contracting the second and fourth index indices. As expected, this gives us a dephasing map. .. plot:: :context: From a5dade9626fc2347047b37e7015eba841c3f21d9 Mon Sep 17 00:00:00 2001 From: christian512 Date: Thu, 25 Aug 2022 10:06:48 +0200 Subject: [PATCH 009/602] add changelog --- doc/changes/1984.doc | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/1984.doc diff --git a/doc/changes/1984.doc b/doc/changes/1984.doc new file mode 100644 index 0000000000..e3fc5ea797 --- /dev/null +++ b/doc/changes/1984.doc @@ -0,0 +1 @@ +Add a guide on Superoperators, Pauli Basis and Channel Contraction. From 9650fcb0a2002c086b6e09ab829775d21ad5e24a Mon Sep 17 00:00:00 2001 From: christian512 Date: Fri, 9 Sep 2022 09:14:14 +0200 Subject: [PATCH 010/602] fix plot context --- doc/guide/guide-super.rst | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/doc/guide/guide-super.rst b/doc/guide/guide-super.rst index 1886488e13..c0cc29fe50 100644 --- a/doc/guide/guide-super.rst +++ b/doc/guide/guide-super.rst @@ -21,10 +21,12 @@ In particular, we will use Hinton diagrams as implemented by :func:`qutip.visual show the real parts of matrix elements as squares whose size and color both correspond to the magnitude of each element. To illustrate, we first plot a few density operators. .. plot:: - :context: + :context: reset + + from qutip import visualization, identity, Qobj, to_super, sigmaz, tensor, hadamrd_transform, cnot, tensor_contract ->>> visualization.hinton(identity([2, 3]).unit()); # doctest: +SKIP ->>> visualization.hinton(Qobj([[1, 0.5], [0.5, 1]]).unit()); # doctest: +SKIP + visualization.hinton(identity([2, 3]).unit()) + visualization.hinton(Qobj([[1, 0.5], [0.5, 1]]).unit()) We show superoperators as matrices in the *Pauli basis*, such that any Hermicity-preserving map is represented by a real-valued matrix. This is especially convienent for use with Hinton diagrams, as the plot thus carries complete information about the channel. @@ -34,7 +36,7 @@ As an example, conjugation by :math:`\sigma_z` leaves :math:`\mathbb{1}` and :ma .. plot:: :context: ->>> visualization.hinton(to_super(sigmaz())); # doctest: +SKIP + visualization.hinton(to_super(sigmaz())) As a couple more examples, we also consider the supermatrix for a Hadamard transform and for :math:`\sigma_z \otimes H`. @@ -42,8 +44,8 @@ As a couple more examples, we also consider the supermatrix for a Hadamard trans .. plot:: :context: ->>> visualization.hinton(to_super(hadamard_transform())); # doctest: +SKIP ->>> visualization.hinton(to_super(tensor(sigmaz(), hadamard_transform()))); # doctest: +SKIP + visualization.hinton(to_super(hadamard_transform())) + visualization.hinton(to_super(tensor(sigmaz(), hadamard_transform()))) .. _super-reduced-channels: @@ -67,26 +69,36 @@ The two tensor wires on the left indicate where we must take a tensor contractio .. plot:: :context: - ->>> tensor_contract(to_super(identity([2, 2])), (1, 3)) # doctest: +SKIP + :nofigs: + tensor_contract(to_super(identity([2, 2])), (1, 3)) Meanwhile, the :func:`super_tensor` function implements the swap on the right, such that we can quickly find the preparation map. ->>> q = tensor(identity(2), basis(2)) # doctest: +SKIP ->>> s_prep = sprepost(q, q.dag()) # doctest: +SKIP +.. plot:: + :context: + :nofigs: + + q = tensor(identity(2), basis(2)) + s_prep = sprepost(q, q.dag()) For a :math:`\scriptstyle \rm CNOT` system-environment model, the composition of these maps should give us a completely dephasing channel. The channel on both qubits is just the superunitary :math:`\scriptstyle \rm CNOT` channel: .. plot:: :context: ->>> visualization.hinton(to_super(cnot())); # doctest: +SKIP + visualization.hinton(to_super(cnot())) We now complete by multiplying the superunitary :math:`\scriptstyle \rm CNOT` by the preparation channel above, then applying the partial trace channel by contracting the second and fourth index indices. As expected, this gives us a dephasing map. .. plot:: :context: ->>> visualization.hinton(tensor_contract(to_super(cnot()), (1, 3)) * s_prep); # doctest: +SKIP + visualization.hinton(tensor_contract(to_super(cnot()), (1, 3)) * s_prep) +.. plot:: + :context: reset + :include-source: false + :nofigs: + + # reset the context at the end \ No newline at end of file From 07a9d1b8768b93cfdcfa151cd02ac45a372b6edf Mon Sep 17 00:00:00 2001 From: christian512 Date: Wed, 14 Sep 2022 10:53:29 +0200 Subject: [PATCH 011/602] fix import of hadamard --- doc/guide/guide-super.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/guide/guide-super.rst b/doc/guide/guide-super.rst index c0cc29fe50..d88446d964 100644 --- a/doc/guide/guide-super.rst +++ b/doc/guide/guide-super.rst @@ -23,7 +23,7 @@ show the real parts of matrix elements as squares whose size and color both corr .. plot:: :context: reset - from qutip import visualization, identity, Qobj, to_super, sigmaz, tensor, hadamrd_transform, cnot, tensor_contract + from qutip import visualization, identity, Qobj, to_super, sigmaz, tensor, hadamard_transform, cnot, tensor_contract visualization.hinton(identity([2, 3]).unit()) visualization.hinton(Qobj([[1, 0.5], [0.5, 1]]).unit()) From b69405e26a1ce7f45a70c8ce6b4bc358303a5fde Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Tue, 4 Oct 2022 10:10:37 +0200 Subject: [PATCH 012/602] Add .fermionic flag to bath exponents. --- qutip/solver/heom/bofin_baths.py | 10 ++++++++-- qutip/tests/solver/heom/test_bofin_baths.py | 5 +++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/qutip/solver/heom/bofin_baths.py b/qutip/solver/heom/bofin_baths.py index 9dfec1a1d7..4d6f84a1d7 100644 --- a/qutip/solver/heom/bofin_baths.py +++ b/qutip/solver/heom/bofin_baths.py @@ -120,15 +120,20 @@ def _check_sigma_bar_k_offset(self, type, offset): " specified for + and - bath exponents" ) + def _type_is_fermionic(self, type): + return type in (self.types["+"], self.types["-"]) + + def __init__( - self, type, dim, Q, ck, vk, ck2=None, sigma_bar_k_offset=None, - tag=None, + self, type, dim, Q, ck, vk, ck2=None, + sigma_bar_k_offset=None, tag=None, ): if not isinstance(type, self.types): type = self.types[type] self._check_ck2(type, ck2) self._check_sigma_bar_k_offset(type, sigma_bar_k_offset) self.type = type + self.fermionic = self._type_is_fermionic(type) self.dim = dim self.Q = Q self.ck = ck @@ -145,6 +150,7 @@ def __repr__(self): f" Q.dims={dims!r}" f" ck={self.ck!r} vk={self.vk!r} ck2={self.ck2!r}" f" sigma_bar_k_offset={self.sigma_bar_k_offset!r}" + f" fermionic={self.fermionic!r}" f" tag={self.tag!r}>" ) diff --git a/qutip/tests/solver/heom/test_bofin_baths.py b/qutip/tests/solver/heom/test_bofin_baths.py index 6900a30933..c6c5c80fc1 100644 --- a/qutip/tests/solver/heom/test_bofin_baths.py +++ b/qutip/tests/solver/heom/test_bofin_baths.py @@ -30,6 +30,7 @@ def check_exponent( ): """ Check the attributes of a BathExponent. """ assert exp.type is BathExponent.types[type] + assert exp.fermionic == (type in ["+", "-"]) assert exp.dim == dim assert exp.Q == Q assert exp.ck == pytest.approx(ck) @@ -98,7 +99,7 @@ def test_repr(self): assert repr(exp1) == ( "" + " sigma_bar_k_offset=None fermionic=False tag=None>" ) exp2 = BathExponent( "+", None, Q=None, ck=1.0, vk=2.0, sigma_bar_k_offset=-1, @@ -107,7 +108,7 @@ def test_repr(self): assert repr(exp2) == ( "" + " sigma_bar_k_offset=-1 fermionic=True tag='bath1'>" ) From 624e2dde8923456c1e9bfd8ff3e5005e7437616e Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Tue, 4 Oct 2022 10:12:45 +0200 Subject: [PATCH 013/602] Only sum over fermionic excitations when calculating the signs of fermionic previous and next gradient terms. --- qutip/solver/heom/bofin_solvers.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/qutip/solver/heom/bofin_solvers.py b/qutip/solver/heom/bofin_solvers.py index 23266f40cd..171718a346 100644 --- a/qutip/solver/heom/bofin_solvers.py +++ b/qutip/solver/heom/bofin_solvers.py @@ -686,12 +686,6 @@ def _combine_bath_exponents(self, bath): exp.type in (exp.types["+"], exp.types["-"]) for exp in exponents ) - if not (all_bosonic or all_fermionic): - raise ValueError( - "Bath exponents are currently restricted to being either" - " all bosonic or all fermionic, but a mixture of bath" - " exponents was given." - ) if not all(exp.Q.dims == exponents[0].Q.dims for exp in exponents): raise ValueError( "All bath exponents must have system coupling operators" @@ -755,11 +749,15 @@ def _grad_prev_bosonic(self, he_n, k): def _grad_prev_fermionic(self, he_n, k): ck = self.ados.ck + he_fermionic_n = [ + i * int(exp.fermionic) + for i, exp in zip(he_n, self.ados.exponents) + ] - n_excite = sum(he_n) + n_excite = sum(he_fermionic_n) sign1 = (-1) ** (n_excite + 1) - n_excite_before_m = sum(he_n[:k]) + n_excite_before_m = sum(he_fermionic_n[:k]) sign2 = (-1) ** (n_excite_before_m) sigma_bar_k = k + self.ados.sigma_bar_k_offset[k] @@ -807,10 +805,14 @@ def _grad_next_bosonic(self, he_n, k): return op def _grad_next_fermionic(self, he_n, k): - n_excite = sum(he_n) + he_fermionic_n = [ + i * int(exp.fermionic) + for i, exp in zip(he_n, self.ados.exponents) + ] + n_excite = sum(he_fermionic_n) sign1 = (-1) ** (n_excite + 1) - n_excite_before_m = sum(he_n[:k]) + n_excite_before_m = sum(he_fermionic_n[:k]) sign2 = (-1) ** (n_excite_before_m) if self.ados.exponents[k].type == BathExponent.types["+"]: From 8059a4d34ec6c63f1b61c672544660095b1cfc17 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Tue, 4 Oct 2022 10:17:52 +0200 Subject: [PATCH 014/602] Add a simple test for a fermionic bath combined with a completely decoupled bosonic bath. --- qutip/tests/solver/heom/test_bofin_solvers.py | 71 ++++++++++++++++--- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/qutip/tests/solver/heom/test_bofin_solvers.py b/qutip/tests/solver/heom/test_bofin_solvers.py index 21d0f8f7fd..764334ee96 100644 --- a/qutip/tests/solver/heom/test_bofin_solvers.py +++ b/qutip/tests/solver/heom/test_bofin_solvers.py @@ -348,10 +348,10 @@ def rho(self): """ Initial state. """ return 0.5 * Qobj(np.ones((2, 2))) - def state_current(self, ado_state): + def state_current(self, ado_state, tags=None): level_1_aux = [ (ado_state.extract(label), ado_state.exps(label)[0]) - for label in ado_state.filter(level=1) + for label in ado_state.filter(level=1, tags=tags) ] def exp_sign(exp): @@ -554,14 +554,6 @@ def test_create_bath_errors(self): BathExponent("R", 2, Q=Q, ck=1.2, vk=2.2), ] - with pytest.raises(ValueError) as err: - HEOMSolver(H, Bath(mixed_types), 2) - assert str(err.value) == ( - "Bath exponents are currently restricted to being either all" - " bosonic or all fermionic, but a mixture of bath exponents was" - " given." - ) - with pytest.raises(ValueError) as err: HEOMSolver(H, Bath(mixed_q_dims), 2) assert str(err.value) == ( @@ -813,6 +805,64 @@ def test_discrete_level_model_lorentzian_baths( # analytic_current = dlm.analytic_current() np.testing.assert_allclose(analytic_current, current, rtol=1e-3) + + @pytest.mark.parametrize(['evo'], [ + pytest.param("qobj"), + pytest.param("qobjevo_const"), + pytest.param("qobjevo_timedep"), + ]) + @pytest.mark.parametrize(['liouvillianize'], [ + pytest.param(False, id="hamiltonian"), + pytest.param(True, id="liouvillian"), + ]) + def test_discrete_level_model_fermionic_bath_with_decoupled_bosonic_bath( + self, evo, liouvillianize + ): + dlm = DiscreteLevelCurrentModel( + gamma=0.01, W=1, T=0.025851991, lmax=10, + ) + H_sys = hamiltonian_to_sys(dlm.H, evo, liouvillianize) + ck_plus, vk_plus, ck_minus, vk_minus = dlm.bath_coefficients() + + options = { + "store_states": True, + "store_ados": True, + "nsteps": 15_000, + "rtol": 1e-7, + "atol": 1e-7, + } + fermionic_bath = FermionicBath( + dlm.Q, ck_plus, vk_plus, ck_minus, vk_minus, tag="fermionic", + ) + # very weak bosonic coupling which should not affect the dynamics of + # the interaction between the system and the fermionic bath: + eps = [1e-10] * 5 + bosonic_Q = sigmax() + bosonic_bath = BosonicBath(bosonic_Q, eps, eps, eps, eps, combine=False) + # for a single impurity we converge with max_depth = 2 + # we specify the bosonic bath first to ensure that the test checks + # that the sums inside HEOMSolver grad-next/prev work when the bosonic + # mode is before the fermionic ones + hsolver = HEOMSolver( + H_sys, [bosonic_bath, fermionic_bath], 2, options=options, + ) + + tlist = [0, 600] + result = hsolver.run(dlm.rho(), tlist) + current = dlm.state_current(result.ado_states[-1], tags=["fermionic"]) + analytic_current = dlm.analytic_current() + np.testing.assert_allclose(analytic_current, current, rtol=1e-3) + + if evo != "qobjevo_timedep": + rho_final, ado_state = hsolver.steady_state() + current = dlm.state_current(ado_state) + analytic_current = dlm.analytic_current() + np.testing.assert_allclose(analytic_current, current, rtol=1e-3) + else: + assert_raises_steady_state_time_dependent(hsolver) + + + @pytest.mark.parametrize(['ado_format'], [ pytest.param("hierarchy-ados-state", id="hierarchy-ados-state"), pytest.param("numpy", id="numpy"), @@ -897,6 +947,7 @@ def test_solving_with_step(self): assert states[-1] == ado_state.extract(0) + class TestHeomsolveFunction: @pytest.mark.parametrize(['evo'], [ pytest.param("qobj", id="qobj"), From 0c55647bdea28a5dc81150aae6c2edf4a0e667e1 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 25 Oct 2022 10:18:51 -0400 Subject: [PATCH 015/602] remove coeff type --- qutip/core/_brtensor.pyx | 2 +- qutip/core/coefficient.py | 57 ++++++++++++++------------ qutip/core/cy/_element.pxd | 26 ++++++------ qutip/core/cy/_element.pyx | 21 +++++++--- qutip/core/cy/coefficient.pyx | 76 +++++------------------------------ qutip/core/cy/qobjevo.pyx | 34 ++++++++++++++-- 6 files changed, 103 insertions(+), 113 deletions(-) diff --git a/qutip/core/_brtensor.pyx b/qutip/core/_brtensor.pyx index ee4d3f2e9f..a40952968f 100644 --- a/qutip/core/_brtensor.pyx +++ b/qutip/core/_brtensor.pyx @@ -269,7 +269,7 @@ cdef class _BlochRedfieldElement(_BaseElement): return Qobj(self.data(t), dims=self.dims, type="super", copy=False, superrep="super") - cpdef double complex coeff(self, double t) except *: + cpdef object coeff(self, double t): return 1. cpdef Data data(self, double t): diff --git a/qutip/core/coefficient.py b/qutip/core/coefficient.py index db9c9fc75e..1119643a67 100644 --- a/qutip/core/coefficient.py +++ b/qutip/core/coefficient.py @@ -24,7 +24,7 @@ from .data import Data from .cy.coefficient import ( Coefficient, InterCoefficient, FunctionCoefficient, StrFunctionCoefficient, - ConjCoefficient, NormCoefficient, ShiftCoefficient + ConjCoefficient, NormCoefficient ) @@ -47,8 +47,20 @@ class StringParsingWarning(Warning): pass +def _return(base, **kwargs): + return base + + +coefficient_builders = { + Coefficient: _return, + np.ndarray: InterCoefficient, + scipy.interpolate.PPoly: InterCoefficient.from_PPoly, + scipy.interpolate.BSpline: InterCoefficient.from_Bspline, +} + + def coefficient(base, *, tlist=None, args={}, args_ctypes={}, - order=3, compile_opt=None, function_style=None): + order=3, compile_opt=None, function_style=None, **kwargs): """Coefficient for time dependent systems. The coefficients are either a function, a string or a numpy array. @@ -117,22 +129,20 @@ def f2_t(t, args): scipy are converted to a function-based coefficient (the same kind of coefficient created from callables). """ - if isinstance(base, Coefficient): - return base - - elif isinstance(base, np.ndarray): - return InterCoefficient(base, tlist, order) - - elif isinstance(base, scipy.interpolate.PPoly): - return InterCoefficient.from_PPoly(base) - - elif isinstance(base, scipy.interpolate.BSpline): - return InterCoefficient.from_Bspline(base) - - elif isinstance(base, str): - return coeff_from_str(base, args, args_ctypes, compile_opt) - - elif callable(base): + kwargs.update({ + "tlist": tlist, + 'args': args, + 'args_ctypes': args_ctypes, + 'order': order, + 'compile_opt': compile_opt, + 'function_style': function_style, + }) + + for type_ in coefficient_builders: + if isinstance(base, type_): + return coefficient_builders[type_](base, **kwargs) + + if callable(base): op = FunctionCoefficient(base, args.copy(), style=function_style) if not isinstance(op(0), numbers.Number): raise TypeError("The coefficient function must return a number") @@ -153,12 +163,6 @@ def conj(coeff): return ConjCoefficient(coeff) -def shift(coeff, _t0=0): - """ return a Coefficient in which t is shifted by _t0. - """ - return ShiftCoefficient(coeff, _t0) - - # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # %%%%%%%%% Everything under this is for string compilation %%%%%%%%% # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -311,7 +315,7 @@ def proj(x): "spe": scipy.special} -def coeff_from_str(base, args, args_ctypes, compile_opt=None): +def coeff_from_str(base, args, args_ctypes, compile_opt=None, **_): """ Entry point for string based coefficients - Test if the string is valid @@ -359,6 +363,9 @@ def coeff_from_str(base, args, args_ctypes, compile_opt=None): return coeff(base, keys, const, args) +coefficient_builders[str] = coeff_from_str + + def try_import(file_name, parsed_in): """ Import the compiled coefficient if existing and check for name collision. diff --git a/qutip/core/cy/_element.pxd b/qutip/core/cy/_element.pxd index 59301e1b7c..85b823a414 100644 --- a/qutip/core/cy/_element.pxd +++ b/qutip/core/cy/_element.pxd @@ -8,7 +8,7 @@ from libcpp cimport bool cdef class _BaseElement: cpdef Data data(self, double t) cpdef object qobj(self, double t) - cpdef double complex coeff(self, double t) except * + cpdef object coeff(self, double t) cdef Data matmul_data_t(_BaseElement self, double t, Data state, Data out=?) @@ -22,21 +22,21 @@ cdef class _EvoElement(_BaseElement): cdef class _FuncElement(_BaseElement): - cdef object _func - cdef dict _args - cdef tuple _previous - cdef bint _f_pythonic - cdef set _f_parameters + cdef readonly object _func + cdef readonly dict _args + cdef readonly tuple _previous + cdef readonly bint _f_pythonic + cdef readonly set _f_parameters cdef class _MapElement(_BaseElement): - cdef _FuncElement _base - cdef list _transform - cdef double complex _coeff + cdef readonly _FuncElement _base + cdef readonly list _transform + cdef readonly double complex _coeff cdef class _ProdElement(_BaseElement): - cdef _BaseElement _left - cdef _BaseElement _right - cdef list _transform - cdef bool _conj + cdef readonly _BaseElement _left + cdef readonly _BaseElement _right + cdef readonly list _transform + cdef readonly bool _conj diff --git a/qutip/core/cy/_element.pyx b/qutip/core/cy/_element.pyx index 8d46d546c8..d1ebba88d8 100644 --- a/qutip/core/cy/_element.pyx +++ b/qutip/core/cy/_element.pyx @@ -93,7 +93,7 @@ cdef class _BaseElement: "Sub-classes of _BaseElement should implement .qobj(t)." ) - cpdef double complex coeff(self, double t) except *: + cpdef object coeff(self, double t): """ Returns the complex coefficient of the term at time ``t``. @@ -256,6 +256,12 @@ cdef class _BaseElement: "Sub-classes of _BaseElement should implement .replace_arguments(t)." ) + def __call__(self, t, args=None): + if args: + cache = [] + self = self.replace_arguments(args, cache) + return self.qobj(t) * self.coeff(t) + cdef class _ConstantElement(_BaseElement): """ @@ -289,7 +295,7 @@ cdef class _ConstantElement(_BaseElement): cpdef object qobj(self, double t): return self._qobj - cpdef double complex coeff(self, double t) except *: + cpdef object coeff(self, double t): return 1. def linear_map(self, f, anti=False): @@ -298,6 +304,9 @@ cdef class _ConstantElement(_BaseElement): def replace_arguments(self, args, cache=None): return self + def __call__(self, t, args=None): + return self._qobj + cdef class _EvoElement(_BaseElement): """ @@ -339,7 +348,7 @@ cdef class _EvoElement(_BaseElement): cpdef object qobj(self, double t): return self._qobj - cpdef double complex coeff(self, double t) except *: + cpdef object coeff(self, double t): return self._coefficient(t) def linear_map(self, f, anti=False): @@ -451,7 +460,7 @@ cdef class _FuncElement(_BaseElement): self._previous = (t, _qobj) return _qobj - cpdef double complex coeff(self, double t) except *: + cpdef object coeff(self, double t): return 1. def linear_map(self, f, anti=False): @@ -523,7 +532,7 @@ cdef class _MapElement(_BaseElement): out = func(out) return out - cpdef double complex coeff(self, double t) except *: + cpdef object coeff(self, double t): return self._coeff def linear_map(self, f, anti=False): @@ -581,7 +590,7 @@ cdef class _ProdElement(_BaseElement): out = func(out) return out - cpdef double complex coeff(self, double t) except *: + cpdef object coeff(self, double t): cdef double complex out = self._left.coeff(t) * self._right.coeff(t) return conj(out) if self._conj else out diff --git a/qutip/core/cy/coefficient.pyx b/qutip/core/cy/coefficient.pyx index 72f031537e..5da2a9815a 100644 --- a/qutip/core/cy/coefficient.pyx +++ b/qutip/core/cy/coefficient.pyx @@ -75,7 +75,7 @@ cdef class Coefficient: :obj:`Coefficient` are immutable. """ - def __init__(self): + def __init__(self, **_): raise NotImplementedError("Only sub-classes should be initiated.") def replace_arguments(self, _args=None, **kwargs): @@ -121,8 +121,8 @@ cdef class Coefficient: cdef double complex _call(self, double t) except *: """Core computation of the :obj:`Coefficient`.""" - raise NotImplementedError("All Coefficient sub-classes " - "should overwrite this.") + # All Coefficient sub-classes should overwrite this or __call__ + return complex(self(t)) def __add__(left, right): if ( @@ -155,10 +155,6 @@ cdef class Coefficient: """ Return a :obj:`Coefficient` being the norm of this""" return NormCoefficient(self) - def _shift(self): - """ Return a :obj:`Coefficient` with a time shift""" - return ShiftCoefficient(self, 0) - @cython.auto_pickle(True) cdef class FunctionCoefficient(Coefficient): @@ -189,7 +185,7 @@ cdef class FunctionCoefficient(Coefficient): _UNSET = object() def __init__(self, func, dict args, style=None, _f_pythonic=_UNSET, - _f_parameters=_UNSET): + _f_parameters=_UNSET, **_): if _f_pythonic is self._UNSET or _f_parameters is self._UNSET: if not (_f_pythonic is self._UNSET and _f_parameters is self._UNSET): @@ -208,7 +204,9 @@ cdef class FunctionCoefficient(Coefficient): self._f_pythonic = _f_pythonic self._f_parameters = _f_parameters - cdef complex _call(self, double t) except *: + def __call__(self, double t, dict _args=None, **kwargs): + if _args is not None or kwargs: + return self.replace_arguments(_args, **kwargs)(t) if self._f_pythonic: return self.func(t, **self.args) return self.func(t, self.args) @@ -325,7 +323,7 @@ cdef class StrFunctionCoefficient(Coefficient): "np": np, "spe": scipy.special} - def __init__(self, base, dict args): + def __init__(self, base, dict args, **_): args2var = "\n".join([" {} = args['{}']".format(key, key) for key in args]) code = f""" @@ -396,7 +394,7 @@ cdef class InterCoefficient(Coefficient): cdef complex[:, :] poly cdef object np_arrays - def __init__(self, coeff_arr, tlist, int order): + def __init__(self, coeff_arr, tlist, int order, **_): tlist = np.array(tlist, dtype=np.float64) coeff_arr = np.array(coeff_arr, dtype=np.complex128) @@ -503,11 +501,11 @@ cdef class InterCoefficient(Coefficient): return out @classmethod - def from_PPoly(cls, ppoly): + def from_PPoly(cls, ppoly, **_): return cls.restore(ppoly.x, ppoly.c) @classmethod - def from_Bspline(cls, spline): + def from_Bspline(cls, spline, **_): tlist = np.unique(spline.t) a = np.arange(spline.k+1) a[0] = 1 @@ -721,55 +719,3 @@ cdef class NormCoefficient(Coefficient): cpdef Coefficient copy(self): """Return a copy of the :obj:`Coefficient`.""" return NormCoefficient(self.base.copy()) - - -@cython.auto_pickle(True) -cdef class ShiftCoefficient(Coefficient): - """ - Introduce a time shift into the :obj:`Coefficient`. - - Used internally within qutip when calculating correlations. - - :obj:ShiftCoefficient is returned by - ``qutip.coefficent.shift(Coefficient)``. - """ - cdef Coefficient base - cdef double _t0 - - def __init__(self, Coefficient base, double _t0): - self.base = base - self._t0 = _t0 - - def replace_arguments(self, _args=None, **kwargs): - """ - Replace the arguments (``args``) of a coefficient. - - Returns a new :obj:`Coefficient` if the coefficient has arguments, or - the original coefficient if it does not. Arguments to replace may be - supplied either in a dictionary as the first position argument, or - passed as keywords, or as a combination of the two. Arguments not - replaced retain their previous values. - - Parameters - ---------- - _args : dict - Dictionary of arguments to replace. - - **kwargs - Arguments to replace. - """ - if _args: - kwargs.update(_args) - try: - _t0 = kwargs["_t0"] - del kwargs["_t0"] - except KeyError: - _t0 = self._t0 - return ShiftCoefficient(self.base.replace_arguments(**kwargs), _t0) - - cdef complex _call(self, double t) except *: - return self.base._call(t + self._t0) - - cpdef Coefficient copy(self): - """Return a copy of the :obj:`Coefficient`.""" - return ShiftCoefficient(self.base.copy(), self._t0) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 1e3b4a1ae5..724175f523 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -774,6 +774,35 @@ cdef class QobjEvo: self.elements = cleaned_elements + def to_list(QobjEvo self): + """ + Restore the QobjEvo to a list form. + + Returns + ------- + list_qevo: list + The QobjEvo as a list, element are either :class:`Qobj` for + constant parts, ``[Qobj, Coefficient]`` for coefficient based term. + The original format of the :class:`Coefficient` is not restored. + Lastly if the original `QobjEvo` is constructed with an function + returning a Qobj, the term is returned as a pair of :class:`Qobj` + and args (``dict``). + """ + out = [] + for element in self.elements: + if isinstance(element, _ConstantElement): + out.append(element.qobj(0)) + elif isinstance(element, _EvoElement): + coeff = element._coefficient + out.append([element.qobj(0), coeff]) + elif isinstance(element, _FuncElement): + func = element._func + args = element._args + out.append([func, args]) + else: + out.append([element, {}]) + return out + ########################################################################### # properties # ########################################################################### @@ -855,7 +884,7 @@ cdef class QobjEvo: if type(state) is Dense: return self._expect_dense(t, state) cdef _BaseElement part - cdef double complex out = 0., coeff + cdef object out = 0. cdef Data part_data cdef object expect_func t = self._prepare(t, state) @@ -868,9 +897,8 @@ cdef class QobjEvo: for element in self.elements: part = (<_BaseElement> element) - coeff = part.coeff(t) part_data = part.data(t) - out += coeff * expect_func(part_data, state) + out += part.coeff(t) * expect_func(part_data, state) return out cdef double complex _expect_dense(QobjEvo self, double t, Dense state) except *: From 96930cc1a10d6e5b0c394623cd0dd7b5ddea2a93 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 28 Oct 2022 17:36:28 -0400 Subject: [PATCH 016/602] pytree qobjevo --- qutip/core/_brtensor.pyx | 8 +++---- qutip/core/cy/_element.pxd | 8 +++---- qutip/core/cy/_element.pyx | 40 +++++++++++++++++------------------ qutip/core/cy/coefficient.pxd | 2 +- qutip/core/cy/coefficient.pyx | 8 +++---- qutip/core/cy/qobjevo.pxd | 6 +++--- qutip/core/cy/qobjevo.pyx | 37 ++++++++++++++++++++++++++------ 7 files changed, 67 insertions(+), 42 deletions(-) diff --git a/qutip/core/_brtensor.pyx b/qutip/core/_brtensor.pyx index a40952968f..530cf63d1b 100644 --- a/qutip/core/_brtensor.pyx +++ b/qutip/core/_brtensor.pyx @@ -265,14 +265,14 @@ cdef class _BlochRedfieldElement(_BaseElement): return _br_term_data(A_eig, self.spectrum, self.skew, cutoff) raise ValueError('Invalid tensortype') - cpdef object qobj(self, double t): + cpdef object qobj(self, t): return Qobj(self.data(t), dims=self.dims, type="super", copy=False, superrep="super") - cpdef object coeff(self, double t): + cpdef object coeff(self, t): return 1. - cpdef Data data(self, double t): + cpdef Data data(self, t): cdef size_t i cdef double cutoff = self.sec_cutoff * self._compute_spectrum(t) A_eig = self.H.to_eigbasis(t, self.a_op._call(t)) @@ -281,7 +281,7 @@ cdef class _BlochRedfieldElement(_BaseElement): return BR_eig return self.H.from_eigbasis(t, BR_eig) - cdef Data matmul_data_t(self, double t, Data state, Data out=None): + cdef Data matmul_data_t(self, t, Data state, Data out=None): cdef size_t i cdef double cutoff = self.sec_cutoff * self._compute_spectrum(t) cdef Data A_eig, BR_eig diff --git a/qutip/core/cy/_element.pxd b/qutip/core/cy/_element.pxd index 85b823a414..46f869b266 100644 --- a/qutip/core/cy/_element.pxd +++ b/qutip/core/cy/_element.pxd @@ -6,10 +6,10 @@ from qutip.core.data.base cimport idxint from libcpp cimport bool cdef class _BaseElement: - cpdef Data data(self, double t) - cpdef object qobj(self, double t) - cpdef object coeff(self, double t) - cdef Data matmul_data_t(_BaseElement self, double t, Data state, Data out=?) + cpdef Data data(self, t) + cpdef object qobj(self, t) + cpdef object coeff(self, t) + cdef Data matmul_data_t(_BaseElement self, t, Data state, Data out=?) cdef class _ConstantElement(_BaseElement): diff --git a/qutip/core/cy/_element.pyx b/qutip/core/cy/_element.pyx index d1ebba88d8..999d51d06d 100644 --- a/qutip/core/cy/_element.pyx +++ b/qutip/core/cy/_element.pyx @@ -55,7 +55,7 @@ cdef class _BaseElement: All :obj:`~_BaseElement` instances are immutable and methods that would modify an object return a new instance instead. """ - cpdef Data data(self, double t): + cpdef Data data(self, t): """ Returns the underlying :obj:`~Data` of the :obj:`~Qobj` component of the term at time ``t``. @@ -75,7 +75,7 @@ cdef class _BaseElement: "Sub-classes of _BaseElement should implement .data(t)." ) - cpdef object qobj(self, double t): + cpdef object qobj(self, t): """ Returns the :obj:`~Qobj` component of the term at time ``t``. @@ -93,7 +93,7 @@ cdef class _BaseElement: "Sub-classes of _BaseElement should implement .qobj(t)." ) - cpdef object coeff(self, double t): + cpdef object coeff(self, t): """ Returns the complex coefficient of the term at time ``t``. @@ -111,7 +111,7 @@ cdef class _BaseElement: "Sub-classes of _BaseElement should implement .coeff(t)." ) - cdef Data matmul_data_t(_BaseElement self, double t, Data state, Data out=None): + cdef Data matmul_data_t(_BaseElement self, t, Data state, Data out=None): """ Possibly in-place multiplication and addition. Multiplies a given state by the elemen's value at time ``t`` and adds the result to ``out``. @@ -289,13 +289,13 @@ cdef class _ConstantElement(_BaseElement): ) return NotImplemented - cpdef Data data(self, double t): + cpdef Data data(self, t): return self._qobj.data - cpdef object qobj(self, double t): + cpdef object qobj(self, t): return self._qobj - cpdef object coeff(self, double t): + cpdef object coeff(self, t): return 1. def linear_map(self, f, anti=False): @@ -342,13 +342,13 @@ cdef class _EvoElement(_BaseElement): return NotImplemented return _EvoElement(left._qobj * right._qobj, coefficient) - cpdef Data data(self, double t): + cpdef Data data(self, t): return self._qobj.data - cpdef object qobj(self, double t): + cpdef object qobj(self, t): return self._qobj - cpdef object coeff(self, double t): + cpdef object coeff(self, t): return self._coefficient(t) def linear_map(self, f, anti=False): @@ -444,10 +444,10 @@ cdef class _FuncElement(_BaseElement): def __matmul__(left, right): return _ProdElement(left, right, []) - cpdef Data data(self, double t): + cpdef Data data(self, t): return self.qobj(t).data - cpdef object qobj(self, double t): + cpdef object qobj(self, t): cdef double _t cdef object _qobj _t, _qobj = self._previous @@ -460,7 +460,7 @@ cdef class _FuncElement(_BaseElement): self._previous = (t, _qobj) return _qobj - cpdef object coeff(self, double t): + cpdef object coeff(self, t): return 1. def linear_map(self, f, anti=False): @@ -523,16 +523,16 @@ cdef class _MapElement(_BaseElement): def __matmul__(left, right): return _ProdElement(left, right, []) - cpdef Data data(self, double t): + cpdef Data data(self, t): return self.qobj(t).data - cpdef object qobj(self, double t): + cpdef object qobj(self, t): out = self._base.qobj(t) for func in self._transform: out = func(out) return out - cpdef object coeff(self, double t): + cpdef object coeff(self, t): return self._coeff def linear_map(self, f, anti=False): @@ -581,20 +581,20 @@ cdef class _ProdElement(_BaseElement): def __matmul__(left, right): return _ProdElement(left, right, []) - cpdef Data data(self, double t): + cpdef Data data(self, t): return self.qobj(t).data - cpdef object qobj(self, double t): + cpdef object qobj(self, t): out = self._left.qobj(t) @ self._right.qobj(t) for func in self._transform: out = func(out) return out - cpdef object coeff(self, double t): + cpdef object coeff(self, t): cdef double complex out = self._left.coeff(t) * self._right.coeff(t) return conj(out) if self._conj else out - cdef Data matmul_data_t(_ProdElement self, double t, Data state, Data out=None): + cdef Data matmul_data_t(_ProdElement self, t, Data state, Data out=None): cdef Data temp if not self._transform: temp = self._right.matmul_data_t(t, state) diff --git a/qutip/core/cy/coefficient.pxd b/qutip/core/cy/coefficient.pxd index d584514839..f81858c2ef 100644 --- a/qutip/core/cy/coefficient.pxd +++ b/qutip/core/cy/coefficient.pxd @@ -1,5 +1,5 @@ #cython: language_level=3 cdef class Coefficient: - cdef dict args + cdef readonly dict args cdef double complex _call(self, double t) except * cpdef Coefficient copy(self) diff --git a/qutip/core/cy/coefficient.pyx b/qutip/core/cy/coefficient.pyx index 5da2a9815a..2a5367d8f7 100644 --- a/qutip/core/cy/coefficient.pyx +++ b/qutip/core/cy/coefficient.pyx @@ -75,8 +75,8 @@ cdef class Coefficient: :obj:`Coefficient` are immutable. """ - def __init__(self, **_): - raise NotImplementedError("Only sub-classes should be initiated.") + def __init__(self, args, **_): + self.args = args def replace_arguments(self, _args=None, **kwargs): """ @@ -98,7 +98,7 @@ cdef class Coefficient: """ return self - def __call__(self, double t, dict _args=None, **kwargs): + def __call__(self, t, dict _args=None, **kwargs): """ Return the coefficient value at time `t`. Stored arguments can overwriten with `_args` or as keywords parameters. @@ -204,7 +204,7 @@ cdef class FunctionCoefficient(Coefficient): self._f_pythonic = _f_pythonic self._f_parameters = _f_parameters - def __call__(self, double t, dict _args=None, **kwargs): + def __call__(self, t, dict _args=None, **kwargs): if _args is not None or kwargs: return self.replace_arguments(_args, **kwargs)(t) if self._f_pythonic: diff --git a/qutip/core/cy/qobjevo.pxd b/qutip/core/cy/qobjevo.pxd index d02f3bf18b..44a92e1433 100644 --- a/qutip/core/cy/qobjevo.pxd +++ b/qutip/core/cy/qobjevo.pxd @@ -16,10 +16,10 @@ cdef class QobjEvo: cpdef Data _call(QobjEvo self, double t) - cdef double _prepare(QobjEvo self, double t, Data state=*) + cdef object _prepare(QobjEvo self, object t, Data state=*) - cpdef double complex expect_data(QobjEvo self, double t, Data state) except * + cpdef object expect_data(QobjEvo self, object t, Data state) cdef double complex _expect_dense(QobjEvo self, double t, Dense state) except * - cpdef Data matmul_data(QobjEvo self, double t, Data state, Data out=*) + cpdef Data matmul_data(QobjEvo self, object t, Data state, Data out=*) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 724175f523..4da9c3ca8b 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -287,6 +287,28 @@ cdef class QobjEvo: return out + @classmethod + def _retore(cls, elements, dims, shape, type, superrep, flags): + # We + cdef QobjEvo out = cls.__new__(cls) + out.elements = elements + out.dims = dims + out.shape = shape + out.type = type + out.superrep = superrep + out._issuper, out._isoper, out._shift_dt = flags + return out + + def _getstate(self): + return { + "elements": self.elements, + "dims": self.dims, + "shape": self.shape, + "type": self.type, + "superrep": self.superrep, + "flags": (self._issuper, self._isoper, self._shift_dt) + } + def __call__(self, double t, dict _args=None, **kwargs): """ Get the :class:`~Qobj` at ``t``. @@ -355,7 +377,7 @@ cdef class QobjEvo: ) return out - cdef double _prepare(QobjEvo self, double t, Data state=None): + cdef object _prepare(QobjEvo self, object t, Data state=None): """ Precomputation before computing getting the element at `t`""" return t + self._shift_dt @@ -837,7 +859,7 @@ cdef class QobjEvo: ########################################################################### # operation methods # ########################################################################### - def expect(QobjEvo self, double t, state): + def expect(QobjEvo self, object t, state, check_real=True): """ Expectation value of this operator at time ``t`` with the state. @@ -870,11 +892,14 @@ cdef class QobjEvo: raise ValueError("incompatible dimensions " + str(self.dims) + ", " + str(state.dims)) out = self.expect_data(t, state.data) - if out == 0 or (out.real and fabs(out.imag / out.real) < herm_rtol): + if ( + check_real and + (out == 0 or (out.real and fabs(out.imag / out.real) < herm_rtol)) + ): return out.real return out - cpdef double complex expect_data(QobjEvo self, double t, Data state) except *: + cpdef object expect_data(QobjEvo self, object t, Data state): """ Expectation is defined as ``state.adjoint() @ self @ state`` if ``state`` is a vector, or ``state`` is an operator and ``self`` is a @@ -930,7 +955,7 @@ cdef class QobjEvo: out += coeff * expect_data_dense(part_data, state) return out - def matmul(self, double t, state): + def matmul(self, t, state): """ Product of this operator at time ``t`` to the state. ``self(t) @ state`` @@ -959,7 +984,7 @@ cdef class QobjEvo: copy=False ) - cpdef Data matmul_data(QobjEvo self, double t, Data state, Data out=None): + cpdef Data matmul_data(QobjEvo self, object t, Data state, Data out=None): """Compute ``out += self(t) @ state``""" cdef _BaseElement part t = self._prepare(t, state) From 770af7c371010b27e9267e4d61097e43e5a50fde Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 1 Nov 2022 09:58:18 -0400 Subject: [PATCH 017/602] default data layer options --- qutip/core/cy/coefficient.pyx | 56 ++++++++++++++++++++++++++++ qutip/core/data/convert.pyx | 14 ++++++- qutip/core/operators.py | 52 +++++++++++++------------- qutip/core/options.py | 9 +++++ qutip/core/states.py | 55 +++++++++++++++------------ qutip/random_objects.py | 21 +++++++---- qutip/tests/core/test_coefficient.py | 2 +- 7 files changed, 150 insertions(+), 59 deletions(-) diff --git a/qutip/core/cy/coefficient.pyx b/qutip/core/cy/coefficient.pyx index 2a5367d8f7..3377938efd 100644 --- a/qutip/core/cy/coefficient.pyx +++ b/qutip/core/cy/coefficient.pyx @@ -155,6 +155,10 @@ cdef class Coefficient: """ Return a :obj:`Coefficient` being the norm of this""" return NormCoefficient(self) + def _shift(self): + """ Return a :obj:`Coefficient` with a time shift""" + return ShiftCoefficient(self, 0) + @cython.auto_pickle(True) cdef class FunctionCoefficient(Coefficient): @@ -719,3 +723,55 @@ cdef class NormCoefficient(Coefficient): cpdef Coefficient copy(self): """Return a copy of the :obj:`Coefficient`.""" return NormCoefficient(self.base.copy()) + + +@cython.auto_pickle(True) +cdef class ShiftCoefficient(Coefficient): + """ + Introduce a time shift into the :obj:`Coefficient`. + + Used internally within qutip when calculating correlations. + + :obj:ShiftCoefficient is returned by + ``qutip.coefficent.shift(Coefficient)``. + """ + cdef Coefficient base + cdef double _t0 + + def __init__(self, Coefficient base, double _t0): + self.base = base + self._t0 = _t0 + + def replace_arguments(self, _args=None, **kwargs): + """ + Replace the arguments (``args``) of a coefficient. + + Returns a new :obj:`Coefficient` if the coefficient has arguments, or + the original coefficient if it does not. Arguments to replace may be + supplied either in a dictionary as the first position argument, or + passed as keywords, or as a combination of the two. Arguments not + replaced retain their previous values. + + Parameters + ---------- + _args : dict + Dictionary of arguments to replace. + + **kwargs + Arguments to replace. + """ + if _args: + kwargs.update(_args) + try: + _t0 = kwargs["_t0"] + del kwargs["_t0"] + except KeyError: + _t0 = self._t0 + return ShiftCoefficient(self.base.replace_arguments(**kwargs), _t0) + + cdef complex _call(self, double t) except *: + return self.base._call(t + self._t0) + + cpdef Coefficient copy(self): + """Return a copy of the :obj:`Coefficient`.""" + return ShiftCoefficient(self.base.copy(), self._t0) diff --git a/qutip/core/data/convert.pyx b/qutip/core/data/convert.pyx index 1bf7713cc2..a2419fc682 100644 --- a/qutip/core/data/convert.pyx +++ b/qutip/core/data/convert.pyx @@ -18,7 +18,7 @@ import numbers import numpy as np from scipy.sparse import dok_matrix, csgraph - +from qutip.settings import settings cimport cython __all__ = ['to', 'create'] @@ -90,6 +90,16 @@ cdef class _partial_converter: return "" +cdef class _default_dtype: + cdef type dtype + + def __init__(self, dtype): + self.dtype = dtype + + def __call__(self): + return settings.core["default_dtype"] or self.dtype + + # While `_to` and `_create` are defined as objects here, they are actually # exported by `data.__init__.py` as singleton function objects of their # respective types (without the leading underscore). @@ -309,6 +319,8 @@ cdef class _to: registered, or if ``dtype`` is a type, but not a known data-layer type. """ + if type(dtype) is _default_dtype: + dtype = dtype() if type(dtype) is type: if dtype not in self.dtypes: raise ValueError( diff --git a/qutip/core/operators.py b/qutip/core/operators.py index 140ff82d59..806243e85c 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -18,9 +18,11 @@ from . import data as _data from .qobj import Qobj from .dimensions import flatten +from .data.convert import _default_dtype -def qdiags(diagonals, offsets=None, dims=None, shape=None, *, dtype=_data.CSR): +def qdiags(diagonals, offsets=None, dims=None, shape=None, *, + dtype=_default_dtype(_data.CSR)): """ Constructs an operator from an array of diagonals. @@ -63,7 +65,7 @@ def qdiags(diagonals, offsets=None, dims=None, shape=None, *, dtype=_data.CSR): return Qobj(data, dims=dims, type='oper', copy=False) -def jmat(j, which=None, *, dtype=_data.CSR): +def jmat(j, which=None, *, dtype=_default_dtype(_data.CSR)): """Higher-order spin operators: Parameters @@ -144,7 +146,7 @@ def jmat(j, which=None, *, dtype=_data.CSR): raise ValueError('Invalid spin operator: ' + which) -def _jplus(j, *, dtype=_data.CSR): +def _jplus(j, *, dtype=_default_dtype(_data.CSR)): """ Internal functions for generating the data representing the J-plus operator. @@ -154,7 +156,7 @@ def _jplus(j, *, dtype=_data.CSR): return _data.diag[dtype](data, 1) -def _jz(j, *, dtype=_data.CSR): +def _jz(j, *, dtype=_default_dtype(_data.CSR)): """ Internal functions for generating the data representing the J-z operator. """ @@ -166,7 +168,7 @@ def _jz(j, *, dtype=_data.CSR): # # Spin j operators: # -def spin_Jx(j, *, dtype=_data.CSR): +def spin_Jx(j, *, dtype=_default_dtype(_data.CSR)): """Spin-j x operator Parameters @@ -187,7 +189,7 @@ def spin_Jx(j, *, dtype=_data.CSR): return jmat(j, 'x', dtype=dtype) -def spin_Jy(j, *, dtype=_data.CSR): +def spin_Jy(j, *, dtype=_default_dtype(_data.CSR)): """Spin-j y operator Parameters @@ -208,7 +210,7 @@ def spin_Jy(j, *, dtype=_data.CSR): return jmat(j, 'y', dtype=dtype) -def spin_Jz(j, *, dtype=_data.CSR): +def spin_Jz(j, *, dtype=_default_dtype(_data.CSR)): """Spin-j z operator Parameters @@ -229,7 +231,7 @@ def spin_Jz(j, *, dtype=_data.CSR): return jmat(j, 'z', dtype=dtype) -def spin_Jm(j, *, dtype=_data.CSR): +def spin_Jm(j, *, dtype=_default_dtype(_data.CSR)): """Spin-j annihilation operator Parameters @@ -250,7 +252,7 @@ def spin_Jm(j, *, dtype=_data.CSR): return jmat(j, '-', dtype=dtype) -def spin_Jp(j, *, dtype=_data.CSR): +def spin_Jp(j, *, dtype=_default_dtype(_data.CSR)): """Spin-j creation operator Parameters @@ -271,7 +273,7 @@ def spin_Jp(j, *, dtype=_data.CSR): return jmat(j, '+', dtype=dtype) -def spin_J_set(j, *, dtype=_data.CSR): +def spin_J_set(j, *, dtype=_default_dtype(_data.CSR)): """Set of spin-j operators (x, y, z) Parameters @@ -384,7 +386,7 @@ def sigmaz(): return _SIGMAZ.copy() -def destroy(N, offset=0, *, dtype=_data.CSR): +def destroy(N, offset=0, *, dtype=_default_dtype(_data.CSR)): """ Destruction (lowering) operator. @@ -422,7 +424,7 @@ def destroy(N, offset=0, *, dtype=_data.CSR): return qdiags(data, 1, dtype=dtype) -def create(N, offset=0, *, dtype=_data.CSR): +def create(N, offset=0, *, dtype=_default_dtype(_data.CSR)): """ Creation (raising) operator. @@ -493,7 +495,7 @@ def _implicit_tensor_dimensions(dimensions): return np.prod(flat), [dimensions, dimensions] -def qzero(dimensions, *, dtype=_data.CSR): +def qzero(dimensions, *, dtype=_default_dtype(_data.CSR)): """ Zero operator. @@ -522,7 +524,7 @@ def qzero(dimensions, *, dtype=_data.CSR): isherm=True, isunitary=False, copy=False) -def qeye(dimensions, *, dtype=_data.CSR): +def qeye(dimensions, *, dtype=_default_dtype(_data.CSR)): """ Identity operator. @@ -572,7 +574,7 @@ def qeye(dimensions, *, dtype=_data.CSR): identity = qeye -def position(N, offset=0, *, dtype=_data.CSR): +def position(N, offset=0, *, dtype=_default_dtype(_data.CSR)): """ Position operator x=1/sqrt(2)*(a+a.dag()) @@ -598,7 +600,7 @@ def position(N, offset=0, *, dtype=_data.CSR): return np.sqrt(0.5) * (a + a.dag()) -def momentum(N, offset=0, *, dtype=_data.CSR): +def momentum(N, offset=0, *, dtype=_default_dtype(_data.CSR)): """ Momentum operator p=-1j/sqrt(2)*(a-a.dag()) @@ -624,7 +626,7 @@ def momentum(N, offset=0, *, dtype=_data.CSR): return -1j * np.sqrt(0.5) * (a - a.dag()) -def num(N, offset=0, *, dtype=_data.CSR): +def num(N, offset=0, *, dtype=_default_dtype(_data.CSR)): """ Quantum object for number operator. @@ -660,7 +662,7 @@ def num(N, offset=0, *, dtype=_data.CSR): return qdiags(data, 0, dtype=dtype) -def squeeze(N, z, offset=0, *, dtype=_data.CSR): +def squeeze(N, z, offset=0, *, dtype=_default_dtype(_data.CSR)): """Single-mode squeezing operator. Parameters @@ -731,7 +733,7 @@ def squeezing(a1, a2, z): return b.expm() -def displace(N, alpha, offset=0, *, dtype=_data.Dense): +def displace(N, alpha, offset=0, *, dtype=_default_dtype(_data.Dense)): """Single-mode displacement operator. Parameters @@ -786,7 +788,7 @@ def commutator(A, B, kind="normal"): raise TypeError("Unknown commutator kind '%s'" % kind) -def qutrit_ops(*, dtype=_data.CSR): +def qutrit_ops(*, dtype=_default_dtype(_data.CSR)): """ Operators for a three level system (qutrit). @@ -815,7 +817,7 @@ def qutrit_ops(*, dtype=_data.CSR): return out -def phase(N, phi0=0, *, dtype=_data.Dense): +def phase(N, phi0=0, *, dtype=_default_dtype(_data.Dense)): """ Single-mode Pegg-Barnett phase operator. @@ -849,7 +851,7 @@ def phase(N, phi0=0, *, dtype=_data.Dense): return Qobj(ops, dims=[[N], [N]], type='oper', copy=False).to(dtype) -def enr_destroy(dims, excitations, *, dtype=_data.CSR): +def enr_destroy(dims, excitations, *, dtype=_default_dtype(_data.CSR)): """ Generate annilation operators for modes in a excitation-number-restricted state space. For example, consider a system consisting of 4 modes, each @@ -913,7 +915,7 @@ def enr_destroy(dims, excitations, *, dtype=_data.CSR): return [Qobj(a, dims=[dims, dims]).to(dtype) for a in a_ops] -def enr_identity(dims, excitations, *, dtype=_data.CSR): +def enr_identity(dims, excitations, *, dtype=_default_dtype(_data.CSR)): """ Generate the identity operator for the excitation-number restricted state space defined by the `dims` and `exciations` arguments. See the @@ -953,7 +955,7 @@ def enr_identity(dims, excitations, *, dtype=_data.CSR): copy=False) -def charge(Nmax, Nmin=None, frac=1, *, dtype=_data.CSR): +def charge(Nmax, Nmin=None, frac=1, *, dtype=_default_dtype(_data.CSR)): """ Generate the diagonal charge operator over charge states from Nmin to Nmax. @@ -991,7 +993,7 @@ def charge(Nmax, Nmin=None, frac=1, *, dtype=_data.CSR): return out -def tunneling(N, m=1, *, dtype=_data.CSR): +def tunneling(N, m=1, *, dtype=_default_dtype(_data.CSR)): r""" Tunneling operator with elements of the form :math:`\\sum |N> + |010..0> + |001..0> + ... |000..1> ] / sqrt(n)`` @@ -1298,7 +1305,7 @@ def w_state(N=3, *, dtype=_data.Dense): return np.sqrt(1 / N) * state -def ghz_state(N=3, *, dtype=_data.Dense): +def ghz_state(N=3, *, dtype=_default_dtype(_data.Dense)): """ Returns the N-qubit GHZ-state: ``[ |00...00> + |11...11> ] / sqrt(2)`` diff --git a/qutip/random_objects.py b/qutip/random_objects.py index b0bcf85c18..978fc27e9e 100644 --- a/qutip/random_objects.py +++ b/qutip/random_objects.py @@ -24,6 +24,7 @@ to_super, to_choi, to_chi, to_kraus, to_stinespring) from .core import data as _data from .core.dimensions import flatten +from .core.data.convert import _default_dtype _RAND = default_rng() @@ -210,7 +211,7 @@ def _merge_shuffle_blocks(blocks, generator): def rand_herm(dimensions, density=0.30, distribution="fill", *, - eigenvalues=(), seed=None, dtype=_data.CSR): + eigenvalues=(), seed=None, dtype=_default_dtype(_data.CSR)): """Creates a random sparse Hermitian quantum object. Parameters @@ -332,7 +333,7 @@ def _rand_herm_dense(N, density, pos_def, generator): def rand_unitary(dimensions, density=1, distribution="haar", *, - seed=None, dtype=_data.Dense): + seed=None, dtype=_default_dtype(_data.Dense)): r"""Creates a random sparse unitary quantum object. Parameters @@ -434,7 +435,7 @@ def _rand_unitary_haar(N, generator): def rand_ket(dimensions, density=1, distribution="haar", *, - seed=None, dtype=_data.Dense): + seed=None, dtype=_default_dtype(_data.Dense)): """Creates a random ket vector. Parameters @@ -494,7 +495,8 @@ def rand_ket(dimensions, density=1, distribution="haar", *, def rand_dm(dimensions, density=0.75, distribution="ginibre", *, - eigenvalues=(), rank=None, seed=None, dtype=_data.CSR): + eigenvalues=(), rank=None, seed=None, + dtype=_default_dtype(_data.CSR)): r"""Creates a random density matrix of the desired dimensions. Parameters @@ -620,7 +622,8 @@ def _rand_dm_ginibre(N, rank, generator): return rho -def rand_kraus_map(dimensions, *, seed=None, dtype=_data.Dense): +def rand_kraus_map(dimensions, *, seed=None, + dtype=_default_dtype(_data.Dense)): """ Creates a random CPTP map on an N-dimensional Hilbert space in Kraus form. @@ -657,7 +660,8 @@ def rand_kraus_map(dimensions, *, seed=None, dtype=_data.Dense): for x in oper_list] -def rand_super(dimensions, *, superrep="super", seed=None, dtype=_data.Dense): +def rand_super(dimensions, *, superrep="super", seed=None, + dtype=_default_dtype(_data.Dense)): """ Returns a randomly drawn superoperator acting on operators acting on N dimensions. @@ -698,7 +702,8 @@ def rand_super(dimensions, *, superrep="super", seed=None, dtype=_data.Dense): def rand_super_bcsz(dimensions, enforce_tp=True, rank=None, *, - superrep="super", seed=None, dtype=_data.CSR): + superrep="super", seed=None, + dtype=_default_dtype(_data.CSR)): """ Returns a random superoperator drawn from the Bruzda et al ensemble for CPTP maps [BCSZ08]_. Note that due to @@ -800,7 +805,7 @@ def rand_super_bcsz(dimensions, enforce_tp=True, rank=None, *, def rand_stochastic(dimensions, density=0.75, kind='left', - *, seed=None, dtype=_data.CSR): + *, seed=None, dtype=_default_dtype(_data.CSR)): """Generates a random stochastic matrix. Parameters diff --git a/qutip/tests/core/test_coefficient.py b/qutip/tests/core/test_coefficient.py index 01847bf8fd..e2f2a851d8 100644 --- a/qutip/tests/core/test_coefficient.py +++ b/qutip/tests/core/test_coefficient.py @@ -4,7 +4,7 @@ import numpy as np import scipy.interpolate as interp from functools import partial -from qutip.core.coefficient import (coefficient, norm, conj, shift, +from qutip.core.coefficient import (coefficient, norm, conj, CompilationOptions, Coefficient, clean_compiled_coefficient ) From 9b62f18f61af8372c86bc332647606be0a58d1cc Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 1 Nov 2022 15:58:22 -0400 Subject: [PATCH 018/602] Add tests --- qutip/core/coefficient.py | 8 +++++++- qutip/core/cy/qobjevo.pyx | 9 +++++++-- qutip/tests/core/test_coefficient.py | 2 +- qutip/tests/core/test_operators.py | 8 ++++++++ qutip/tests/core/test_qobjevo.py | 19 +++++++++++++++++++ qutip/tests/core/test_states.py | 8 ++++++++ qutip/tests/test_random.py | 26 +++++++++++++++++++++++++- 7 files changed, 75 insertions(+), 5 deletions(-) diff --git a/qutip/core/coefficient.py b/qutip/core/coefficient.py index 1119643a67..0200960622 100644 --- a/qutip/core/coefficient.py +++ b/qutip/core/coefficient.py @@ -24,7 +24,7 @@ from .data import Data from .cy.coefficient import ( Coefficient, InterCoefficient, FunctionCoefficient, StrFunctionCoefficient, - ConjCoefficient, NormCoefficient + ConjCoefficient, NormCoefficient, ShiftCoefficient ) @@ -163,6 +163,12 @@ def conj(coeff): return ConjCoefficient(coeff) +def shift(coeff, _t0=0): + """ return a Coefficient in which t is shifted by _t0. + """ + return ShiftCoefficient(coeff, _t0) + + # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # %%%%%%%%% Everything under this is for string compilation %%%%%%%%% # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 4da9c3ca8b..14b0ada2fd 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -288,8 +288,8 @@ cdef class QobjEvo: return out @classmethod - def _retore(cls, elements, dims, shape, type, superrep, flags): - # We + def _restore(cls, elements, dims, shape, type, superrep, flags): + """ Recreate a QobjEvo without using __init__ """ cdef QobjEvo out = cls.__new__(cls) out.elements = elements out.dims = dims @@ -300,6 +300,11 @@ cdef class QobjEvo: return out def _getstate(self): + """ Obtain the state """ + # For jax pytree representation + # auto_pickle create similar method __getstate__, but since it's + # automatically created, it could change depending on cython version + # etc. so we create our own. return { "elements": self.elements, "dims": self.dims, diff --git a/qutip/tests/core/test_coefficient.py b/qutip/tests/core/test_coefficient.py index e2f2a851d8..01847bf8fd 100644 --- a/qutip/tests/core/test_coefficient.py +++ b/qutip/tests/core/test_coefficient.py @@ -4,7 +4,7 @@ import numpy as np import scipy.interpolate as interp from functools import partial -from qutip.core.coefficient import (coefficient, norm, conj, +from qutip.core.coefficient import (coefficient, norm, conj, shift, CompilationOptions, Coefficient, clean_compiled_coefficient ) diff --git a/qutip/tests/core/test_operators.py b/qutip/tests/core/test_operators.py index 664eb2fc0f..896744da5e 100644 --- a/qutip/tests/core/test_operators.py +++ b/qutip/tests/core/test_operators.py @@ -271,6 +271,14 @@ def test_operator_type(func, args, alias, dtype): for obj in object: assert isinstance(obj.data, dtype) + with qutip.CoreOptions(default_dtype=alias): + object = func(*args) + if isinstance(object, qutip.Qobj): + assert isinstance(object.data, dtype) + else: + for obj in object: + assert isinstance(obj.data, dtype) + @pytest.mark.parametrize('dims', [8, 15, [2] * 4]) def test_qft(dims): diff --git a/qutip/tests/core/test_qobjevo.py b/qutip/tests/core/test_qobjevo.py index f5a04b2c87..7599ea5d17 100644 --- a/qutip/tests/core/test_qobjevo.py +++ b/qutip/tests/core/test_qobjevo.py @@ -301,6 +301,14 @@ def test_QobjEvo_pickle(all_qevo): recreated = pickle.loads(pickled) _assert_qobjevo_equivalent(recreated, obj) +def test_QobjEvo_restore(all_qevo): + "QobjEvo pickle" + # used in parallel_map + obj = all_qevo + state = obj._getstate() + recreated = QobjEvo._restore(**state) + _assert_qobjevo_equivalent(recreated, obj) + def test_shift(all_qevo): dt = 0.2 obj = all_qevo @@ -444,3 +452,14 @@ def test_QobjEvo_isherm_flag_knowcase(): assert QobjEvo([sigmax(), "1j"])(0)._isherm is None assert QobjEvo([[sigmax(), "t"], [sigmaz(), "1"]])(0)._isherm is True assert QobjEvo([[sigmax(), "t"], [sigmaz(), "1j"]])(0)._isherm is None + +@pytest.mark.parametrize( + "coeff_type", + ['func_coeff', 'string', 'array', 'logarray'] +) +def test_QobjEvo_to_list(coeff_type, pseudo_qevo): + qevo = QobjEvo(*pseudo_qevo[coeff_type]) + as_list = qevo.to_list() + assert len(as_list) == 2 + restored = QobjEvo(as_list) + _assert_qobjevo_equivalent(qevo, restored) diff --git a/qutip/tests/core/test_states.py b/qutip/tests/core/test_states.py index e790d06dd8..be17941dec 100644 --- a/qutip/tests/core/test_states.py +++ b/qutip/tests/core/test_states.py @@ -308,3 +308,11 @@ def test_state_type(func, args, alias, dtype): else: for obj in object: assert isinstance(obj.data, dtype) + + with qutip.CoreOptions(default_dtype=alias): + object = func(*args) + if isinstance(object, qutip.Qobj): + assert isinstance(object.data, dtype) + else: + for obj in object: + assert isinstance(obj.data, dtype) diff --git a/qutip/tests/test_random.py b/qutip/tests/test_random.py index fcbf1b4280..13f9554bbf 100644 --- a/qutip/tests/test_random.py +++ b/qutip/tests/test_random.py @@ -4,7 +4,7 @@ import scipy.linalg as la import pytest -from qutip import qeye, num, to_kraus, kraus_to_choi, CoreOptions +from qutip import qeye, num, to_kraus, kraus_to_choi, CoreOptions, Qobj from qutip import data as _data from qutip.random_objects import ( rand_herm, @@ -285,3 +285,27 @@ def test_kraus_map(dimensions, dtype): _assert_metadata(kmap[0], dimensions, dtype) with CoreOptions(atol=1e-9): assert kraus_to_choi(kmap).iscptp + + +dtype_names = list(_data.to._str2type.keys()) + list(_data.to.dtypes) +dtype_types = list(_data.to._str2type.values()) + list(_data.to.dtypes) +@pytest.mark.parametrize(['alias', 'dtype'], zip(dtype_names, dtype_types), + ids=[str(dtype) for dtype in dtype_names]) +@pytest.mark.parametrize('func', [ + rand_herm, + rand_unitary, + rand_dm, + rand_ket, + rand_stochastic, + rand_super, + rand_super_bcsz, + rand_kraus_map, +]) +def test_random_dtype(func, alias, dtype): + with CoreOptions(default_dtype=alias): + object = func(2) + if isinstance(object, Qobj): + assert isinstance(object.data, dtype) + else: + for obj in object: + assert isinstance(obj.data, dtype) From e0db8e7eb9a24be9d4bedd44d8b739f19428d21a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 20 Dec 2022 11:14:55 -0500 Subject: [PATCH 019/602] Stochastic for v5 idea --- qutip/core/data/trace.pxd | 3 + qutip/core/data/trace.pyx | 48 ++++++++ qutip/solver/mcsolve.py | 4 +- qutip/solver/multitraj.py | 2 +- qutip/solver/sode/__init__.py | 0 qutip/solver/sode/sode.pyx | 130 ++++++++++++++++++++++ qutip/solver/sode/ssystem.pyx | 96 ++++++++++++++++ qutip/tests/core/data/test_mathematics.py | 20 ++++ 8 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 qutip/solver/sode/__init__.py create mode 100644 qutip/solver/sode/sode.pyx create mode 100644 qutip/solver/sode/ssystem.pyx diff --git a/qutip/core/data/trace.pxd b/qutip/core/data/trace.pxd index bdac487763..e231896f8f 100644 --- a/qutip/core/data/trace.pxd +++ b/qutip/core/data/trace.pxd @@ -4,3 +4,6 @@ from qutip.core.data cimport CSR, Dense cpdef double complex trace_csr(CSR matrix) nogil except * cpdef double complex trace_dense(Dense matrix) nogil except * + +cpdef double complex trace_oper_ket_csr(CSR matrix) nogil except * +cpdef double complex trace_oper_ket_dense(Dense matrix) nogil except * diff --git a/qutip/core/data/trace.pyx b/qutip/core/data/trace.pyx index 18756de52d..565b9b2c26 100644 --- a/qutip/core/data/trace.pyx +++ b/qutip/core/data/trace.pyx @@ -2,11 +2,14 @@ #cython: boundscheck=False, wraparound=False, initializedcheck=False cimport cython +from libc.math cimport sqrt from qutip.core.data cimport Data, CSR, Dense +from qutip.core.data cimport base __all__ = [ 'trace', 'trace_csr', 'trace_dense', + 'trace_oper_ket', 'trace_oper_ket_csr', 'trace_oper_ket_dense', ] @@ -17,6 +20,13 @@ cdef void _check_shape(Data matrix) nogil except *: ])) +cdef void _check_shape_oper_ket(int N, Data matrix) nogil except *: + if matrix.shape[0] != N * N or matrix.shape[1] != 1: + raise ValueError("".join([ + "matrix ", str(matrix.shape), " is not a stacked square matrix." + ])) + + cpdef double complex trace_csr(CSR matrix) nogil except *: _check_shape(matrix) cdef size_t row, ptr @@ -39,6 +49,28 @@ cpdef double complex trace_dense(Dense matrix) nogil except *: return trace +cpdef double complex trace_oper_ket_csr(CSR matrix) nogil except *: + cdef int N = sqrt(matrix.shape[0]) + _check_shape_oper_ket(N, matrix) + cdef size_t row, ptr + cdef double complex trace = 0 + for row in range(N): + if matrix.row_index[row * (N+1)] != matrix.row_index[row * (N+1) + 1]: + trace += matrix.data[matrix.row_index[row * (N+1)]] + return trace + +cpdef double complex trace_oper_ket_dense(Dense matrix) nogil except *: + cdef int N = sqrt(matrix.shape[0]) + _check_shape_oper_ket(N, matrix) + cdef double complex trace = 0 + cdef size_t ptr = 0 + cdef size_t stride = N + 1 + for _ in range(N): + trace += matrix.data[ptr] + ptr += stride + return trace + + from .dispatch import Dispatcher as _Dispatcher import inspect as _inspect @@ -58,4 +90,20 @@ trace.add_specialisations([ (Dense, trace_dense), ], _defer=True) +trace_oper_ket = _Dispatcher( + _inspect.Signature([ + _inspect.Parameter('matrix', _inspect.Parameter.POSITIONAL_OR_KEYWORD), + ]), + name='trace_oper_ket', + module=__name__, + inputs=('matrix',), + out=False, +) +trace_oper_ket.__doc__ =\ + """Compute the trace (sum of digaonal elements) of a stacked square matrix .""" +trace_oper_ket.add_specialisations([ + (CSR, trace_oper_ket_csr), + (Dense, trace_oper_ket_dense), +], _defer=True) + del _inspect, _Dispatcher diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 24458ce5dc..825b9eab58 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -223,12 +223,12 @@ def reset(self, hard=False): def _prob_func(self, state): if self.issuper: - return _data.norm.trace(unstack_columns(state)) + return _data.norm.trace_oper_ket(state) return _data.norm.l2(state)**2 def _norm_func(self, state): if self.issuper: - return _data.norm.trace(unstack_columns(state)) + return _data.norm.trace_oper_ket(state) return _data.norm.l2(state) def _find_collapse_time(self, norm_old, norm, t_prev, t_final): diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 73f84de66b..372ac2f675 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -6,7 +6,7 @@ import numpy as np from copy import copy -__all__ = ["MultiTrajSolver", "TrajectorySolver"] +__all__ = ["MultiTrajSolver"] class MultiTrajSolver(Solver): diff --git a/qutip/solver/sode/__init__.py b/qutip/solver/sode/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/qutip/solver/sode/sode.pyx b/qutip/solver/sode/sode.pyx new file mode 100644 index 0000000000..2d72a85ec4 --- /dev/null +++ b/qutip/solver/sode/sode.pyx @@ -0,0 +1,130 @@ + + +from qutip.core import data as _data +from qutip.core.cy.qobjevo cimport QobjEvo +from qutip.core.data cimport Data + +cdef class StochasticIntegrator: + cdef: + StochasticSystem system + double dt + double t + bool support_time_dependant + bool supports_blackbox + + def __init__(self, system, dt, options): + self.system = system + self.dt = dt + self.options = options + + def set_state(self, t, state0, seed): + """ + Set the state of the ODE solver. + + Parameters + ---------- + t : float + Initial time + + state0 : qutip.Data + Initial state. + + .. note: + It should set the flags `_is_set` to True. + """ + raise NotImplementedError + + def integrate(self, t, copy=True): + """ + Evolve to t. + + Before calling `integrate` for the first time, the initial state should + be set with `set_state`. + + Parameters + ---------- + t : float + Time to integrate to, should be larger than the previous time. + + copy : bool [True] + Whether to return a copy of the state or the state itself. + + Returns + ------- + (t, state) : (float, qutip.Data) + The state of the solver at ``t``. + """ + raise NotImplementedError + + def get_state(self, copy=True): + """ + Obtain the state of the solver as a pair (t, state). + + Parameters + ---------- + copy : bool (True) + Whether to return the data stored in the Integrator or a copy. + + Returns + ------- + (t, state) : (float, qutip.Data) + The state of the solver at ``t``. + """ + raise NotImplementedError + + def run(self, tlist): + """ + Integrate the system yielding the state for each times in tlist. + + Parameters + ---------- + tlist : *list* / *array* + List of times to yield the state. + + Yields + ------ + (t, state) : (float, qutip.Data) + The state of the solver at each ``t`` of tlist. + """ + for t in tlist[1:]: + yield self.integrate(t, False) + + def reset(self, hard=False): + """Reset internal state of the ODE solver.""" + if self._is_set: + state = self.get_state() + if hard: + self._prepare() + if self._is_set: + self.set_state(*state) + + + def arguments(self, args): + """ + Change the argument of the system. + Reset the ODE solver to ensure numerical validity. + + Parameters + ---------- + args : dict + New arguments + """ + self.system.arguments(args) + self.reset() + + @property + def options(self): + # Options should be overwritten by each integrators. + return self._options + + @options.setter + def options(self, new_options): + # This does not apply the new options. + self._options = { + **self._options, + **{ + key: new_options[key] + for key in self.integrator_options.keys() + if key in new_options and new_options[key] is not None + } + } diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx new file mode 100644 index 0000000000..1d6ee8a94a --- /dev/null +++ b/qutip/solver/sode/ssystem.pyx @@ -0,0 +1,96 @@ +""" +Class to represent a stochastic differential equation system. +""" + +from qutip.core import data as _data +from qutip.core.cy.qobjevo cimport QobjEvo +from qutip.core.data cimport Data + +cdef class StochasticSystem: + """ + + """ + def __init__(self, a, b): + self.a = a + self.b = b + + def drift(self, t, state): + return self.b(t, state) + + def diffusion(self, t, state): + return self.a(t, state) + + + +cdef class StochasticClosedSystem: + """ + + """ + cdef QobjEvo H + cdef list c_ops + cdef list cpcd_ops + cdef object imp + cdef Data state + cdef double t + + def __init__(self, H, c_ops, dt, options=None, implicit=False): + self.H = H + self.c_ops = c_ops + self.cpcd_ops = [op + op.dag() for op in c_ops] + self.dt = dt + + def drift(self, double t, Data state): + cdef int i + cdef QobjEvo c_op + cdef Data temp, out + + out = self.L.matmul_data(t, state) + for i in range(len(self.c_ops)): + c_op = self.cpcd_ops[i] + e = c_op.expect_data(t, state) + c_op = self.c_ops[i] + temp = c_op.matmul_data(t, state) + out = _data.add(out, state, -0.125 * e * e) + out = _data.add(out, temp, 0.5 * e) + + def diffusion(self, double t, Data state): + cdef int i + cdef QobjEvo c_op + out = [] + for i in range(len(self.c_ops)): + c_op = self.c_ops[i] + _out = c_op.matmul_data(t, state) + c_op = self.cpcd_ops[i] + expect = c_op.expect_data(t, state) + out.append(_data.add(out, state, -0.5 * expect)) + return out + + +cdef class StochasticOpenSystem: + """ + + """ + cdef QobjEvo L + cdef list c_ops + cdef object imp + cdef Data state + cdef double t + + def __init__(self, H, c_ops): + self.L = H + self.c_ops = c_ops + + def drift(self, double t, Data state): + return self.L.matmul_data(t, state) + + def diffusion(self, double t, Data state): + cdef int i, k + cdef QobjEvo c_op + cdef complex expect + cdef out = [] + for i in range(self.num_ops): + c_op = self.c_ops[i] + vec = c_op.matmul_data(t, state) + expect = _data.trace_oper_ket(vec) + out.append(_data.add(vec, state, -expect)) + return out diff --git a/qutip/tests/core/data/test_mathematics.py b/qutip/tests/core/data/test_mathematics.py index ceeecda9a7..459ab0d467 100644 --- a/qutip/tests/core/data/test_mathematics.py +++ b/qutip/tests/core/data/test_mathematics.py @@ -804,6 +804,26 @@ def op_numpy(self, matrix): ] +class TestTrace_oper_ket(UnaryOpMixin): + def op_numpy(self, matrix): + N = int(matrix.shape[0] ** 0.5) + return np.sum(np.diag(matrix.reshape((N, N)))) + + shapes = [ + (pytest.param((100, 1), id="oper-ket"),), + ] + bad_shapes = [ + (pytest.param((1, 100), id="bra"),), + (pytest.param((99, 1), id="ket"),), + (pytest.param((99, 99), id="ket"),), + (pytest.param((2, 99), id="nonsquare"),), + ] + specialisations = [ + pytest.param(data.trace_oper_ket_csr, CSR, complex), + pytest.param(data.trace_oper_ket_dense, Dense, complex), + ] + + class TestPow(UnaryOpMixin): def op_numpy(self, matrix, n): return np.linalg.matrix_power(matrix, n) From 0f1cab09e57def5bcbedf2df66629d9377fcdcf4 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 20 Dec 2022 11:16:13 -0500 Subject: [PATCH 020/602] Better idea --- qutip/solver/sode/sode.pyx | 2 -- qutip/solver/sode/ssystem.pyx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/qutip/solver/sode/sode.pyx b/qutip/solver/sode/sode.pyx index 2d72a85ec4..7b5c44c855 100644 --- a/qutip/solver/sode/sode.pyx +++ b/qutip/solver/sode/sode.pyx @@ -1,5 +1,3 @@ - - from qutip.core import data as _data from qutip.core.cy.qobjevo cimport QobjEvo from qutip.core.data cimport Data diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index 1d6ee8a94a..1a88b1c2ca 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -84,7 +84,7 @@ cdef class StochasticOpenSystem: return self.L.matmul_data(t, state) def diffusion(self, double t, Data state): - cdef int i, k + cdef int i cdef QobjEvo c_op cdef complex expect cdef out = [] From 0a3052ce1f524e3f7f2888a957bf6fc41697b4f2 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 20 Dec 2022 17:34:47 -0500 Subject: [PATCH 021/602] first sode integrator --- qutip/solver/sode/sode.py | 131 +++++++++++++++++++++++++ qutip/solver/sode/sode.pyx | 190 +++++++++++++----------------------- qutip/solver/stochastics.py | 85 ++++++++++++++++ 3 files changed, 283 insertions(+), 123 deletions(-) create mode 100644 qutip/solver/sode/sode.py create mode 100644 qutip/solver/stochastics.py diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py new file mode 100644 index 0000000000..e4562cd5f4 --- /dev/null +++ b/qutip/solver/sode/sode.py @@ -0,0 +1,131 @@ + + + +class EulerIntegrator: + def __init__(self, system, options): + self.system = system + self.dt = dt + self.options = options + + def _step(self, dt, dWs): + t = self._t + state = self._state + a = self.system.drift(t, state) + bs = self.diffusion(t, state) + new_state = _data.add(state, a, dt) + for b, dW in zip(bs, dWs): + new_state = _data.add(new_state, b, dW) + self._state = new_state + self._t += dt + + def set_state(self, t, state0, generator): + """ + Set the state of the ODE solver. + + Parameters + ---------- + t : float + Initial time + + state0 : qutip.Data + Initial state. + + .. note: + It should set the flags `_is_set` to True. + """ + raise NotImplementedError + + def integrate(self, t, copy=True): + """ + Evolve to t. + + Before calling `integrate` for the first time, the initial state should + be set with `set_state`. + + Parameters + ---------- + t : float + Time to integrate to, should be larger than the previous time. + + copy : bool [True] + Whether to return a copy of the state or the state itself. + + Returns + ------- + (t, state) : (float, qutip.Data) + The state of the solver at ``t``. + """ + raise NotImplementedError + + def get_state(self, copy=True): + """ + Obtain the state of the solver as a pair (t, state). + + Parameters + ---------- + copy : bool (True) + Whether to return the data stored in the Integrator or a copy. + + Returns + ------- + (t, state) : (float, qutip.Data) + The state of the solver at ``t``. + """ + raise NotImplementedError + + def run(self, tlist): + """ + Integrate the system yielding the state for each times in tlist. + + Parameters + ---------- + tlist : *list* / *array* + List of times to yield the state. + + Yields + ------ + (t, state) : (float, qutip.Data) + The state of the solver at each ``t`` of tlist. + """ + for t in tlist[1:]: + yield self.integrate(t, False) + + def reset(self, hard=False): + """Reset internal state of the ODE solver.""" + if self._is_set: + state = self.get_state() + if hard: + self._prepare() + if self._is_set: + self.set_state(*state) + + + def arguments(self, args): + """ + Change the argument of the system. + Reset the ODE solver to ensure numerical validity. + + Parameters + ---------- + args : dict + New arguments + """ + self.system.arguments(args) + self.reset() + + @property + def options(self): + # Options should be overwritten by each integrators. + return self._options + + @options.setter + def options(self, new_options): + # This does not apply the new options. + self._options = { + **self._options, + **{ + key: new_options[key] + for key in self.integrator_options.keys() + if key in new_options and new_options[key] is not None + } + } diff --git a/qutip/solver/sode/sode.pyx b/qutip/solver/sode/sode.pyx index 7b5c44c855..acbdd28a3d 100644 --- a/qutip/solver/sode/sode.pyx +++ b/qutip/solver/sode/sode.pyx @@ -2,127 +2,71 @@ from qutip.core import data as _data from qutip.core.cy.qobjevo cimport QobjEvo from qutip.core.data cimport Data -cdef class StochasticIntegrator: - cdef: - StochasticSystem system - double dt - double t - bool support_time_dependant - bool supports_blackbox - def __init__(self, system, dt, options): - self.system = system - self.dt = dt - self.options = options - - def set_state(self, t, state0, seed): - """ - Set the state of the ODE solver. - - Parameters - ---------- - t : float - Initial time - - state0 : qutip.Data - Initial state. - - .. note: - It should set the flags `_is_set` to True. - """ - raise NotImplementedError - - def integrate(self, t, copy=True): - """ - Evolve to t. - - Before calling `integrate` for the first time, the initial state should - be set with `set_state`. - - Parameters - ---------- - t : float - Time to integrate to, should be larger than the previous time. - - copy : bool [True] - Whether to return a copy of the state or the state itself. - - Returns - ------- - (t, state) : (float, qutip.Data) - The state of the solver at ``t``. - """ - raise NotImplementedError - - def get_state(self, copy=True): - """ - Obtain the state of the solver as a pair (t, state). - - Parameters - ---------- - copy : bool (True) - Whether to return the data stored in the Integrator or a copy. - - Returns - ------- - (t, state) : (float, qutip.Data) - The state of the solver at ``t``. - """ - raise NotImplementedError - - def run(self, tlist): - """ - Integrate the system yielding the state for each times in tlist. - - Parameters - ---------- - tlist : *list* / *array* - List of times to yield the state. - - Yields - ------ - (t, state) : (float, qutip.Data) - The state of the solver at each ``t`` of tlist. - """ - for t in tlist[1:]: - yield self.integrate(t, False) - - def reset(self, hard=False): - """Reset internal state of the ODE solver.""" - if self._is_set: - state = self.get_state() - if hard: - self._prepare() - if self._is_set: - self.set_state(*state) - - - def arguments(self, args): - """ - Change the argument of the system. - Reset the ODE solver to ensure numerical validity. - - Parameters - ---------- - args : dict - New arguments - """ - self.system.arguments(args) - self.reset() - - @property - def options(self): - # Options should be overwritten by each integrators. - return self._options - - @options.setter - def options(self, new_options): - # This does not apply the new options. - self._options = { - **self._options, - **{ - key: new_options[key] - for key in self.integrator_options.keys() - if key in new_options and new_options[key] is not None - } - } +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef Data euler(StochasticSystem system, t, Data state, dt, dW): + """ + Integration scheme: + Basic Euler order 0.5 + dV = d1 dt + d2_i dW_i + Numerical Solution of Stochastic Differential Equations + By Peter E. Kloeden, Eckhard Platen + """ + cdef int i + a = system.drift(t, state) + b = system.diffusion(t, state) + new_state = _data.add(state, a, dt) + for i in range(system.num_collapse): + new_state = _data.add(new_state, b[i], dW[i]) + return new_state + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef Data platen(StochasticSystem system, t, Data state, dt, dW): + """ + Platen rhs function for both master eq and schrodinger eq. + dV = -iH* (V+Vt)/2 * dt + (d1(V)+d1(Vt))/2 * dt + + (2*d2_i(V)+d2_i(V+)+d2_i(V-))/4 * dW_i + + (d2_i(V+)-d2_i(V-))/4 * (dW_i**2 -dt) * dt**(-.5) + + Vt = V -iH*V*dt + d1*dt + d2_i*dW_i + V+/- = V -iH*V*dt + d1*dt +/- d2_i*dt**.5 + The Theory of Open Quantum Systems + Chapter 7 Eq. (7.47), H.-P Breuer, F. Petruccione + """ + cdef int i, j + cdef double sqrt_dt = np.sqrt(dt) + cdef double sqrt_dt_inv = 0.25/sqrt_dt + cdef double dw, dw2 + + d1 = _data.add(system.drift(t, state), state) + d2 = system.diffusion(t, state) + + out = _data.mul(d1, 0.5) + Vt = d1.copy() + Vp = [] + Vm = [] + for i in range(system.num_collapse): + Vp.append(_data.add(d1, d2[i], sqrt_dt)) + Vm.append(_data.add(d1, d2[i], -sqrt_dt)) + Vt = _data.add(Vt, d2[i], dW[i]) + + d1 = system.drift(t, Vt) + out = _data.add(out, d1, 0.5) + out = _data.add(out, state, 0.5) + for i in range(system.num_collapse): + d2p = system.diffusion(t, Vp[i]) + d2m = system.diffusion(t, Vm[i]) + dw = dW[i] * 0.25 + out = _data.add(out, d2m[i], dw) + out = _data.add(out, d2[i], 2*dw) + out = _data.add(out, d2p[i], dw) + + for j in range(system.num_collapse): + dw2 = sqrt_dt_inv * (dW[i] * dW[j] - dt * (i == j)) + out = _data.add(out, d2p[j], dw) + out = _data.add(out, d2m[j], -dw) + + return out diff --git a/qutip/solver/stochastics.py b/qutip/solver/stochastics.py new file mode 100644 index 0000000000..0bfa090a18 --- /dev/null +++ b/qutip/solver/stochastics.py @@ -0,0 +1,85 @@ +__all__ = ["smesolve", "SMESolver"] + + + + + + +class SIntegrator(Integrator): + def set_state(self, t, state0, generator): + """ + Set the state of the SODE solver. + + Parameters + ---------- + t : float + Initial time + + state0 : qutip.Data + Initial state. + + generator : numpy.random.generator + Random number generator. + """ + raise NotImplementedError + + + + +def smesolve(H, rho0, times, c_ops=(), sc_ops=(), e_ops=(), m_ops=(), + args={}, options=None): + """ + Solve stochastic master equation. Dispatch to specific solvers + depending on the value of the `solver` keyword argument. + + Parameters + ---------- + + H : :class:`qutip.Qobj`, or time dependent system. + System Hamiltonian. + Can depend on time, see StochasticSolverOptions help for format. + + rho0 : :class:`qutip.Qobj` + Initial density matrix or state vector (ket). + + times : *list* / *array* + List of times for :math:`t`. Must be uniformly spaced. + + c_ops : list of :class:`qutip.Qobj`, or time dependent Qobjs. + Deterministic collapse operator which will contribute with a standard + Lindblad type of dissipation. + Can depend on time, see StochasticSolverOptions help for format. + + sc_ops : list of :class:`qutip.Qobj`, or time dependent Qobjs. + List of stochastic collapse operators. Each stochastic collapse + operator will give a deterministic and stochastic contribution + to the eqaution of motion according to how the d1 and d2 functions + are defined. + Can depend on time, see StochasticSolverOptions help for format. + + e_ops : list of :class:`qutip.Qobj` + Single operator or list of operators for which to evaluate + expectation values. + + args : dict + ... + + Returns + ------- + + output: :class:`qutip.solver.Result` + + An instance of the class :class:`qutip.solver.Result`. + + """ + + + + + + + + + +class SMESolver(MultiTrajSolver): + pass From c774be85b34542e022a5a2c997342e44243ad697 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 22 Dec 2022 15:25:01 -0500 Subject: [PATCH 022/602] Start SMESolver --- qutip/solver/multitraj.py | 4 +- qutip/solver/sode/{sode.pyx => _sode.pyx} | 6 +- qutip/solver/sode/sode.py | 151 +++++----------------- qutip/solver/stochastics.py | 130 +++++++++++++++++-- 4 files changed, 152 insertions(+), 139 deletions(-) rename qutip/solver/sode/{sode.pyx => _sode.pyx} (93%) diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 372ac2f675..8f5cbc4184 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -28,7 +28,7 @@ class MultiTrajSolver(Solver): options : dict Options for the solver. """ - name = "generic multi trajectory" + name = "stochastic master equation" resultclass = MultiTrajResult _avail_integrators = {} @@ -40,7 +40,7 @@ class MultiTrajSolver(Solver): "store_states": None, "keep_runs_results": False, "normalize_output": False, - "method": "", + "method": "adams", "map": "serial", "job_timeout": None, "num_cpus": None, diff --git a/qutip/solver/sode/sode.pyx b/qutip/solver/sode/_sode.pyx similarity index 93% rename from qutip/solver/sode/sode.pyx rename to qutip/solver/sode/_sode.pyx index acbdd28a3d..daa736e5cc 100644 --- a/qutip/solver/sode/sode.pyx +++ b/qutip/solver/sode/_sode.pyx @@ -61,12 +61,12 @@ cpdef Data platen(StochasticSystem system, t, Data state, dt, dW): d2m = system.diffusion(t, Vm[i]) dw = dW[i] * 0.25 out = _data.add(out, d2m[i], dw) - out = _data.add(out, d2[i], 2*dw) + out = _data.add(out, d2[i], 2 * dw) out = _data.add(out, d2p[i], dw) for j in range(system.num_collapse): dw2 = sqrt_dt_inv * (dW[i] * dW[j] - dt * (i == j)) - out = _data.add(out, d2p[j], dw) - out = _data.add(out, d2m[j], -dw) + out = _data.add(out, d2p[j], dw2) + out = _data.add(out, d2m[j], -dw2) return out diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index e4562cd5f4..7dcd20e46e 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -1,131 +1,42 @@ +import ._sode as sstepper -class EulerIntegrator: + +class ExplicitIntegrator(SIntegrator): + integrator_options = { + "dt": 0.001, + "tol": 1e-7, + } def __init__(self, system, options): self.system = system - self.dt = dt self.options = options + self.dt = self.options["dt"] + self.tol = self.options["tol"] + self.stepper = getattr(sstepper, options["method"]) - def _step(self, dt, dWs): - t = self._t - state = self._state - a = self.system.drift(t, state) - bs = self.diffusion(t, state) - new_state = _data.add(state, a, dt) - for b, dW in zip(bs, dWs): - new_state = _data.add(new_state, b, dW) - self._state = new_state - self._t += dt + def _step(self, dt, dW): + new_state = self.stepper(self.system, self.t, self.state, dt, dW) + self.state = new_state + self.t += dt def set_state(self, t, state0, generator): - """ - Set the state of the ODE solver. - - Parameters - ---------- - t : float - Initial time - - state0 : qutip.Data - Initial state. - - .. note: - It should set the flags `_is_set` to True. - """ - raise NotImplementedError + self.t = t + self.state = state0 + self.generator = generator def integrate(self, t, copy=True): - """ - Evolve to t. - - Before calling `integrate` for the first time, the initial state should - be set with `set_state`. - - Parameters - ---------- - t : float - Time to integrate to, should be larger than the previous time. - - copy : bool [True] - Whether to return a copy of the state or the state itself. - - Returns - ------- - (t, state) : (float, qutip.Data) - The state of the solver at ``t``. - """ - raise NotImplementedError - - def get_state(self, copy=True): - """ - Obtain the state of the solver as a pair (t, state). - - Parameters - ---------- - copy : bool (True) - Whether to return the data stored in the Integrator or a copy. - - Returns - ------- - (t, state) : (float, qutip.Data) - The state of the solver at ``t``. - """ - raise NotImplementedError - - def run(self, tlist): - """ - Integrate the system yielding the state for each times in tlist. - - Parameters - ---------- - tlist : *list* / *array* - List of times to yield the state. - - Yields - ------ - (t, state) : (float, qutip.Data) - The state of the solver at each ``t`` of tlist. - """ - for t in tlist[1:]: - yield self.integrate(t, False) - - def reset(self, hard=False): - """Reset internal state of the ODE solver.""" - if self._is_set: - state = self.get_state() - if hard: - self._prepare() - if self._is_set: - self.set_state(*state) - - - def arguments(self, args): - """ - Change the argument of the system. - Reset the ODE solver to ensure numerical validity. - - Parameters - ---------- - args : dict - New arguments - """ - self.system.arguments(args) - self.reset() - - @property - def options(self): - # Options should be overwritten by each integrators. - return self._options - - @options.setter - def options(self, new_options): - # This does not apply the new options. - self._options = { - **self._options, - **{ - key: new_options[key] - for key in self.integrator_options.keys() - if key in new_options and new_options[key] is not None - } - } + delta_t = (t - self.t) + dt = self.dt + N, err = np.divmod(delta_t, dt) + if err > self.tol: + # Not a whole number of steps. + N += 1 + dt = delta_t / N + dW = self.generator.normal(0, np.sqrt(dt), size=(N, self.system.num_dw)) + for i in range(N): + self._step(dt, dW[i, :]) + return self.t, self.state, np.sum(dW, axis=0) / (N * dt) + + def get_state(self, copy=True): + return self.t, self.state, self.generator diff --git a/qutip/solver/stochastics.py b/qutip/solver/stochastics.py index 0bfa090a18..481812ce45 100644 --- a/qutip/solver/stochastics.py +++ b/qutip/solver/stochastics.py @@ -1,8 +1,8 @@ __all__ = ["smesolve", "SMESolver"] - - - +from .integrator import Integrator +from .result import MultiTrajResult, Result +from .multitraj import MultiTrajSolver class SIntegrator(Integrator): @@ -24,10 +24,61 @@ def set_state(self, t, state0, generator): raise NotImplementedError + def integrate(self, t, copy=True): + """ + Evolve to t. + + Before calling `integrate` for the first time, the initial state should + be set with `set_state`. + + Parameters + ---------- + t : float + Time to integrate to, should be larger than the previous time. + + copy : bool [True] + Whether to return a copy of the state or the state itself. + + Returns + ------- + (t, state, noise) : (float, qutip.Data, np.ndarray) + The state of the solver at ``t``. + """ + raise NotImplementedError + + + def mcstep(self, t, copy=True): + raise NotImplementedError + + +class StochasticTrajResult(Result): + def _post_init(self, m_ops=()): + super()._post_init() + self.noise = [] + self.m_ops = m_ops + self.measurements = [[] for _ in range(len(m_ops))] + self.add_processor(self._add_measurement) + + def _add_measurement(self, t, state, noise): + expects = [m_op.expect(t, state) for m_op in self.m_ops] + noises = np.sum(noise, axis=0) + for measure, expect, dW in zip(self.measurements, expects, noises): + measure.append(expect + dW) + + def add(t, state, noise): + super().add(t, state) + self._add_measurement(t, state, noise) + self.noise.append(noise) + + +class StochasticResult(MultiTrajResult): + @property + def measurement(self): + return [] -def smesolve(H, rho0, times, c_ops=(), sc_ops=(), e_ops=(), m_ops=(), - args={}, options=None): +def smesolve(H, rho0, tlist, c_ops=(), sc_ops=(), e_ops=(), m_ops=(), + args={}, ntraj=500, options=None): """ Solve stochastic master equation. Dispatch to specific solvers depending on the value of the `solver` keyword argument. @@ -42,7 +93,7 @@ def smesolve(H, rho0, times, c_ops=(), sc_ops=(), e_ops=(), m_ops=(), rho0 : :class:`qutip.Qobj` Initial density matrix or state vector (ket). - times : *list* / *array* + tlist : *list* / *array* List of times for :math:`t`. Must be uniformly spaced. c_ops : list of :class:`qutip.Qobj`, or time dependent Qobjs. @@ -61,6 +112,10 @@ def smesolve(H, rho0, times, c_ops=(), sc_ops=(), e_ops=(), m_ops=(), Single operator or list of operators for which to evaluate expectation values. + m_ops : list of :class:`qutip.Qobj` + Single operator or list of operators for which to evaluate + expectation values. + args : dict ... @@ -72,14 +127,61 @@ def smesolve(H, rho0, times, c_ops=(), sc_ops=(), e_ops=(), m_ops=(), An instance of the class :class:`qutip.solver.Result`. """ - - - - - - - + L = QobjEvo(H, args=args) + c_ops = [QobjEvo(c_op, args=args) for c_op in c_ops] + sc_ops = [QobjEvo(c_op, args=args) for c_op in sc_ops] + L = liouvillian(H, c_ops) + sol = SMESolver(L, sc_ops, options=options) + return sol.run(rho0, tlist, e_ops, m_ops, ntraj) class SMESolver(MultiTrajSolver): - pass + name = "generic multi trajectory" + resultclass = StochasticResult + _avail_integrators = {} + solver_options = { + "progress_bar": "text", + "progress_kwargs": {"chunk_size": 10}, + "store_final_state": False, + "store_states": None, + "keep_runs_results": False, + "normalize_output": False, + "method": "platen", + "map": "serial", + "job_timeout": None, + "num_cpus": None, + "bitgenerator": None, + "heterodyne": False, + } + + def __init__(self, H, sc_ops, *, options=None): + if isinstance(sc_ops, (Qobj, QobjEvo)): + sc_ops = [sc_ops] + sc_ops = [QobjEvo(c_op) for c_op in sc_ops] + if not H.issuper: + L = liouvillian(H, sc_ops) + else: + L = H + sum(lindblad_dissipator(c_op) for c_op in sc_ops) + rhs = StochasticOpenSystem(L, sc_ops) + super().__init__(rhs, options=options) + + if self.options["heterodyne"]: + self.m_ops += [ + sc_op + sc_op.dag(), -1j * (sc_op - sc_op.dag()) + for sc_op in sc_ops + ] + else: + self.m_ops = [sc_op + sc_op.dag() for sc_op in sc_ops] + + def _run_one_traj(self, seed, state, tlist, e_ops): + """ + Run one trajectory and return the result. + """ + result = StochasticTrajResult(e_ops, self.options, m_ops=self.m_ops) + generator = self._get_generator(seed) + self._integrator.set_state(tlist[0], state, generator) + result.add(tlist[0], self._restore_state(state, copy=False), None) + for t in tlist[1:]: + t, state, noise = self._integrator.integrate(t, copy=False) + result.add(t, self._restore_state(state, copy=False), noise) + return seed, result From e8e8b5fc36cf49a5b7958be2cb637a7fdc8c58b2 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 12 Jan 2023 09:41:26 -0500 Subject: [PATCH 023/602] Working --- qutip/solver/integrator.py | 82 +++++++++++++++++++++++++++++++++++ qutip/solver/sode/_sode.pyx | 18 +++++--- qutip/solver/sode/sode.py | 14 +++--- qutip/solver/sode/ssystem.pyx | 66 ++++++++++++++++++---------- qutip/solver/stochastics.py | 50 +-------------------- 5 files changed, 147 insertions(+), 83 deletions(-) diff --git a/qutip/solver/integrator.py b/qutip/solver/integrator.py index 44df5546f1..23ab8b0039 100644 --- a/qutip/solver/integrator.py +++ b/qutip/solver/integrator.py @@ -238,3 +238,85 @@ def options(self, new_options): if key in new_options and new_options[key] is not None } } + + +class SIntegrator(Integrator): + """ + A wrapper around stochastic ODE solvers. + + Parameters + ---------- + system: qutip.StochasticSystem + Quantum system in which states evolve. + + options: dict + Options for the integrator. + + Class Attributes + ---------------- + name : str + The name of the integrator. + + supports_blackbox : bool + If True, then the integrator calls only ``system.matmul``, + ``system.matmul_data``, ``system.expect``, ``system.expect_data`` and + ``isconstant``, ``isoper`` or ``issuper``. This allows the solver using + the integrator to modify the system in creative ways. In particular, + the solver may modify the system depending on *both* the time ``t`` + *and* the current ``state`` the system is being applied to. + + If the integrator calls any other methods, set to False. + + supports_time_dependent : bool + If True, then the integrator supports time dependent systems. If False, + ``supports_blackbox`` should usually be ``False`` too. + + integrator_options : dict + A dictionary of options used by the integrator and their default + values. Once initiated, ``self.options`` will be a dict with the same + keys, not the full options object passed to the solver. Options' keys + included here will be supported by the :cls:SolverOdeOptions. + """ + def set_state(self, t, state0, generator): + """ + Set the state of the SODE solver. + + Parameters + ---------- + t : float + Initial time + + state0 : qutip.Data + Initial state. + + generator : numpy.random.generator + Random number generator. + """ + raise NotImplementedError + + + def integrate(self, t, copy=True): + """ + Evolve to t. + + Before calling `integrate` for the first time, the initial state should + be set with `set_state`. + + Parameters + ---------- + t : float + Time to integrate to, should be larger than the previous time. + + copy : bool [True] + Whether to return a copy of the state or the state itself. + + Returns + ------- + (t, state, noise) : (float, qutip.Data, np.ndarray) + The state of the solver at ``t``. + """ + raise NotImplementedError + + + def mcstep(self, t, copy=True): + raise NotImplementedError diff --git a/qutip/solver/sode/_sode.pyx b/qutip/solver/sode/_sode.pyx index daa736e5cc..09b72b5b7c 100644 --- a/qutip/solver/sode/_sode.pyx +++ b/qutip/solver/sode/_sode.pyx @@ -1,11 +1,17 @@ +#cython: language_level=3 from qutip.core import data as _data from qutip.core.cy.qobjevo cimport QobjEvo from qutip.core.data cimport Data +from collections import defaultdict +cimport cython +import numpy as np + +N_dws = defaultdict(lambda : 1) @cython.boundscheck(False) @cython.wraparound(False) -cpdef Data euler(StochasticSystem system, t, Data state, dt, dW): +cpdef Data euler(system, t, Data state, double dt, double[:, :] dW): """ Integration scheme: Basic Euler order 0.5 @@ -18,13 +24,13 @@ cpdef Data euler(StochasticSystem system, t, Data state, dt, dW): b = system.diffusion(t, state) new_state = _data.add(state, a, dt) for i in range(system.num_collapse): - new_state = _data.add(new_state, b[i], dW[i]) + new_state = _data.add(new_state, b[i], dW[i, 0]) return new_state @cython.boundscheck(False) @cython.wraparound(False) -cpdef Data platen(StochasticSystem system, t, Data state, dt, dW): +cpdef Data platen(system, t, Data state, double dt, double[:, :] dW): """ Platen rhs function for both master eq and schrodinger eq. dV = -iH* (V+Vt)/2 * dt + (d1(V)+d1(Vt))/2 * dt @@ -38,7 +44,7 @@ cpdef Data platen(StochasticSystem system, t, Data state, dt, dW): """ cdef int i, j cdef double sqrt_dt = np.sqrt(dt) - cdef double sqrt_dt_inv = 0.25/sqrt_dt + cdef double sqrt_dt_inv = 0.25 / sqrt_dt cdef double dw, dw2 d1 = _data.add(system.drift(t, state), state) @@ -59,13 +65,13 @@ cpdef Data platen(StochasticSystem system, t, Data state, dt, dW): for i in range(system.num_collapse): d2p = system.diffusion(t, Vp[i]) d2m = system.diffusion(t, Vm[i]) - dw = dW[i] * 0.25 + dw = dW[i, 0] * 0.25 out = _data.add(out, d2m[i], dw) out = _data.add(out, d2[i], 2 * dw) out = _data.add(out, d2p[i], dw) for j in range(system.num_collapse): - dw2 = sqrt_dt_inv * (dW[i] * dW[j] - dt * (i == j)) + dw2 = sqrt_dt_inv * (dW[i, 0] * dW[j, 0] - dt * (i == j)) out = _data.add(out, d2p[j], dw2) out = _data.add(out, d2m[j], -dw2) diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 7dcd20e46e..8a7c3b4f4a 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -1,6 +1,6 @@ -import ._sode as sstepper - +from . import _sode as sstepper +from ..integrator import SIntegrator class ExplicitIntegrator(SIntegrator): @@ -14,6 +14,8 @@ def __init__(self, system, options): self.dt = self.options["dt"] self.tol = self.options["tol"] self.stepper = getattr(sstepper, options["method"]) + self.N_dw = sstepper.N_dws[options["method"]] + self.N_drift = system.num_collapse def _step(self, dt, dW): new_state = self.stepper(self.system, self.t, self.state, dt, dW) @@ -28,14 +30,16 @@ def set_state(self, t, state0, generator): def integrate(self, t, copy=True): delta_t = (t - self.t) dt = self.dt - N, err = np.divmod(delta_t, dt) - if err > self.tol: + N, extra = np.divmod(delta_t, dt) + if extra > self.tol: # Not a whole number of steps. N += 1 dt = delta_t / N - dW = self.generator.normal(0, np.sqrt(dt), size=(N, self.system.num_dw)) + dW = self.generator.normal(0, np.sqrt(dt), size=(N, self.N_drift, self.N_dw)) + for i in range(N): self._step(dt, dW[i, :]) + return self.t, self.state, np.sum(dW, axis=0) / (N * dt) def get_state(self, copy=True): diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index 1a88b1c2ca..f84ace2e22 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -1,3 +1,4 @@ +#cython: language_level=3 """ Class to represent a stochastic differential equation system. """ @@ -5,39 +6,51 @@ Class to represent a stochastic differential equation system. from qutip.core import data as _data from qutip.core.cy.qobjevo cimport QobjEvo from qutip.core.data cimport Data +cimport cython +import numpy as np +from qutip.core import spre, spost cdef class StochasticSystem: - """ + cdef object d1, d2 + cdef int num_collapse + cdef double t + cdef double state - """ def __init__(self, a, b): - self.a = a - self.b = b + self.d1 = a + self.d2 = b + self.num_collapse = 1 def drift(self, t, state): - return self.b(t, state) + return self.d1(t, state) def diffusion(self, t, state): - return self.a(t, state) + return self.d2(t, state) + + + cdef void set_state(self, double t, Data state): + self.t = t + self.state = state + cdef Data a(self, double t, Data state): + return self.d1(self.t, self.state) + cdef object b(self, double t, Data state): + return self.d2(self.t, self.state) -cdef class StochasticClosedSystem: - """ - """ + +cdef class StochasticClosedSystem(StochasticSystem): cdef QobjEvo H cdef list c_ops cdef list cpcd_ops cdef object imp - cdef Data state - cdef double t - def __init__(self, H, c_ops, dt, options=None, implicit=False): + def __init__(self, H, c_ops, heterodyne, implicit=False): self.H = H self.c_ops = c_ops self.cpcd_ops = [op + op.dag() for op in c_ops] - self.dt = dt + self.num_collapse = len(c_ops) def drift(self, double t, Data state): cdef int i @@ -45,7 +58,7 @@ cdef class StochasticClosedSystem: cdef Data temp, out out = self.L.matmul_data(t, state) - for i in range(len(self.c_ops)): + for i in range(self.num_collapse): c_op = self.cpcd_ops[i] e = c_op.expect_data(t, state) c_op = self.c_ops[i] @@ -57,7 +70,7 @@ cdef class StochasticClosedSystem: cdef int i cdef QobjEvo c_op out = [] - for i in range(len(self.c_ops)): + for i in range(self.num_collapse): c_op = self.c_ops[i] _out = c_op.matmul_data(t, state) c_op = self.cpcd_ops[i] @@ -66,19 +79,24 @@ cdef class StochasticClosedSystem: return out -cdef class StochasticOpenSystem: - """ - - """ +cdef class StochasticOpenSystem(StochasticSystem): cdef QobjEvo L cdef list c_ops cdef object imp - cdef Data state - cdef double t - def __init__(self, H, c_ops): + + def __init__(self, H, c_ops, heterodyne): self.L = H - self.c_ops = c_ops + if heterodyne: + self.c_ops = [] + for c in c_ops: + self.c_ops += [ + (spre(c) + spost(c.dag())) / np.sqrt(2), + (spre(c) - spost(c.dag())) * -1j / np.sqrt(2) + ] + else: + self.c_ops = [spre(op) + spost(op.dag()) for op in c_ops] + self.num_collapse = len(self.c_ops) def drift(self, double t, Data state): return self.L.matmul_data(t, state) @@ -88,7 +106,7 @@ cdef class StochasticOpenSystem: cdef QobjEvo c_op cdef complex expect cdef out = [] - for i in range(self.num_ops): + for i in range(self.num_collapse): c_op = self.c_ops[i] vec = c_op.matmul_data(t, state) expect = _data.trace_oper_ket(vec) diff --git a/qutip/solver/stochastics.py b/qutip/solver/stochastics.py index 481812ce45..2e5219b244 100644 --- a/qutip/solver/stochastics.py +++ b/qutip/solver/stochastics.py @@ -1,56 +1,10 @@ __all__ = ["smesolve", "SMESolver"] -from .integrator import Integrator +from .sode.sode import ExplicitIntegrator from .result import MultiTrajResult, Result from .multitraj import MultiTrajSolver -class SIntegrator(Integrator): - def set_state(self, t, state0, generator): - """ - Set the state of the SODE solver. - - Parameters - ---------- - t : float - Initial time - - state0 : qutip.Data - Initial state. - - generator : numpy.random.generator - Random number generator. - """ - raise NotImplementedError - - - def integrate(self, t, copy=True): - """ - Evolve to t. - - Before calling `integrate` for the first time, the initial state should - be set with `set_state`. - - Parameters - ---------- - t : float - Time to integrate to, should be larger than the previous time. - - copy : bool [True] - Whether to return a copy of the state or the state itself. - - Returns - ------- - (t, state, noise) : (float, qutip.Data, np.ndarray) - The state of the solver at ``t``. - """ - raise NotImplementedError - - - def mcstep(self, t, copy=True): - raise NotImplementedError - - class StochasticTrajResult(Result): def _post_init(self, m_ops=()): super()._post_init() @@ -162,7 +116,7 @@ def __init__(self, H, sc_ops, *, options=None): L = liouvillian(H, sc_ops) else: L = H + sum(lindblad_dissipator(c_op) for c_op in sc_ops) - rhs = StochasticOpenSystem(L, sc_ops) + rhs = StochasticOpenSystem(L, sc_ops, self.options["heterodyne"]) super().__init__(rhs, options=options) if self.options["heterodyne"]: From de8a752838b43515b7c315342697c73bad963566 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 18 Jan 2023 15:46:40 -0500 Subject: [PATCH 024/602] Simple smesolve run working --- qutip/solver/sode/_sode.pyx | 6 +- qutip/solver/sode/sode.py | 43 ++++++++- qutip/solver/sode/ssystem.pyx | 23 +++-- .../solver/{stochastics.py => stochastic.py} | 94 +++++++++++++------ 4 files changed, 122 insertions(+), 44 deletions(-) rename qutip/solver/{stochastics.py => stochastic.py} (67%) diff --git a/qutip/solver/sode/_sode.pyx b/qutip/solver/sode/_sode.pyx index 09b72b5b7c..92afe83927 100644 --- a/qutip/solver/sode/_sode.pyx +++ b/qutip/solver/sode/_sode.pyx @@ -47,7 +47,7 @@ cpdef Data platen(system, t, Data state, double dt, double[:, :] dW): cdef double sqrt_dt_inv = 0.25 / sqrt_dt cdef double dw, dw2 - d1 = _data.add(system.drift(t, state), state) + d1 = _data.add(state, system.drift(t, state), dt) d2 = system.diffusion(t, state) out = _data.mul(d1, 0.5) @@ -57,10 +57,10 @@ cpdef Data platen(system, t, Data state, double dt, double[:, :] dW): for i in range(system.num_collapse): Vp.append(_data.add(d1, d2[i], sqrt_dt)) Vm.append(_data.add(d1, d2[i], -sqrt_dt)) - Vt = _data.add(Vt, d2[i], dW[i]) + Vt = _data.add(Vt, d2[i], dW[i, 0]) d1 = system.drift(t, Vt) - out = _data.add(out, d1, 0.5) + out = _data.add(out, d1, 0.5 * dt) out = _data.add(out, state, 0.5) for i in range(system.num_collapse): d2p = system.diffusion(t, Vp[i]) diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 8a7c3b4f4a..2161ce386b 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -1,15 +1,19 @@ - +import numpy as np from . import _sode as sstepper -from ..integrator import SIntegrator - +from ..integrator.integrator import SIntegrator, Integrator +from ..stochastic import StochasticSolver class ExplicitIntegrator(SIntegrator): + """ + Stochastic evolution solver + """ integrator_options = { "dt": 0.001, "tol": 1e-7, } def __init__(self, system, options): self.system = system + self._options = self.integrator_options.copy() self.options = options self.dt = self.options["dt"] self.tol = self.options["tol"] @@ -29,13 +33,23 @@ def set_state(self, t, state0, generator): def integrate(self, t, copy=True): delta_t = (t - self.t) + if delta_t < 0: + raise ValueError("Stochastic integration time") + elif delta_t == 0: + return self.t, self.state, np.zeros((0, self.N_drift, self.N_dw)) + dt = self.dt N, extra = np.divmod(delta_t, dt) + N = int(N) if extra > self.tol: # Not a whole number of steps. N += 1 dt = delta_t / N - dW = self.generator.normal(0, np.sqrt(dt), size=(N, self.N_drift, self.N_dw)) + dW = self.generator.normal( + 0, + np.sqrt(dt), + size=(N, self.N_drift, self.N_dw) + ) for i in range(N): self._step(dt, dW[i, :]) @@ -44,3 +58,24 @@ def integrate(self, t, copy=True): def get_state(self, copy=True): return self.t, self.state, self.generator + + @property + def options(self): + """ + Supported options by verner method: + + dt : float, default=0.001 + Internal time step. + + tol : float, default=1e-7 + Relative tolerance. + """ + return self._options + + @options.setter + def options(self, new_options): + Integrator.options.fset(self, new_options) + + +StochasticSolver.add_integrator(ExplicitIntegrator, "euler") +StochasticSolver.add_integrator(ExplicitIntegrator, "platen") diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index f84ace2e22..de7546b4f9 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -10,11 +10,19 @@ cimport cython import numpy as np from qutip.core import spre, spost +__all__ = [ + "StochasticSystem", "StochasticOpenSystem", "StochasticClosedSystem" +] + + + cdef class StochasticSystem: cdef object d1, d2 - cdef int num_collapse + cdef readonly int num_collapse cdef double t cdef double state + cdef readonly bint issuper + cdef readonly object dims def __init__(self, a, b): self.d1 = a @@ -27,18 +35,10 @@ cdef class StochasticSystem: def diffusion(self, t, state): return self.d2(t, state) - cdef void set_state(self, double t, Data state): self.t = t self.state = state - cdef Data a(self, double t, Data state): - return self.d1(self.t, self.state) - - cdef object b(self, double t, Data state): - return self.d2(self.t, self.state) - - cdef class StochasticClosedSystem(StochasticSystem): cdef QobjEvo H @@ -51,6 +51,8 @@ cdef class StochasticClosedSystem(StochasticSystem): self.c_ops = c_ops self.cpcd_ops = [op + op.dag() for op in c_ops] self.num_collapse = len(c_ops) + self.issuper = False + self.dims = self.H.dims def drift(self, double t, Data state): cdef int i @@ -84,7 +86,6 @@ cdef class StochasticOpenSystem(StochasticSystem): cdef list c_ops cdef object imp - def __init__(self, H, c_ops, heterodyne): self.L = H if heterodyne: @@ -97,6 +98,8 @@ cdef class StochasticOpenSystem(StochasticSystem): else: self.c_ops = [spre(op) + spost(op.dag()) for op in c_ops] self.num_collapse = len(self.c_ops) + self.issuper = True + self.dims = self.L.dims def drift(self, double t, Data state): return self.L.matmul_data(t, state) diff --git a/qutip/solver/stochastics.py b/qutip/solver/stochastic.py similarity index 67% rename from qutip/solver/stochastics.py rename to qutip/solver/stochastic.py index 2e5219b244..39ed014674 100644 --- a/qutip/solver/stochastics.py +++ b/qutip/solver/stochastic.py @@ -1,8 +1,10 @@ __all__ = ["smesolve", "SMESolver"] -from .sode.sode import ExplicitIntegrator +from .sode.ssystem import * from .result import MultiTrajResult, Result from .multitraj import MultiTrajSolver +from ..import Qobj, QobjEvo, liouvillian, lindblad_dissipator +import numpy as np class StochasticTrajResult(Result): @@ -11,7 +13,6 @@ def _post_init(self, m_ops=()): self.noise = [] self.m_ops = m_ops self.measurements = [[] for _ in range(len(m_ops))] - self.add_processor(self._add_measurement) def _add_measurement(self, t, state, noise): expects = [m_op.expect(t, state) for m_op in self.m_ops] @@ -19,9 +20,10 @@ def _add_measurement(self, t, state, noise): for measure, expect, dW in zip(self.measurements, expects, noises): measure.append(expect + dW) - def add(t, state, noise): + def add(self, t, state, noise): super().add(t, state) - self._add_measurement(t, state, noise) + if noise is not None: + self._add_measurement(t, state, noise) self.noise.append(noise) @@ -85,12 +87,55 @@ def smesolve(H, rho0, tlist, c_ops=(), sc_ops=(), e_ops=(), m_ops=(), c_ops = [QobjEvo(c_op, args=args) for c_op in c_ops] sc_ops = [QobjEvo(c_op, args=args) for c_op in sc_ops] L = liouvillian(H, c_ops) - sol = SMESolver(L, sc_ops, options=options) - return sol.run(rho0, tlist, e_ops, m_ops, ntraj) + sol = SMESolver(L, sc_ops, options=options or {}, m_ops=m_ops) + return sol.run(rho0, tlist, ntraj, e_ops=e_ops) -class SMESolver(MultiTrajSolver): - name = "generic multi trajectory" +class StochasticSolver(MultiTrajSolver): + name = "generic stochastic" + resultclass = StochasticResult + _avail_integrators = {} + solver_options = { + "progress_bar": "text", + "progress_kwargs": {"chunk_size": 10}, + "store_final_state": False, + "store_states": None, + "keep_runs_results": False, + "normalize_output": False, + "method": "euler", + "map": "serial", + "job_timeout": None, + "num_cpus": None, + "bitgenerator": None, + } + + def _run_one_traj(self, seed, state, tlist, e_ops): + """ + Run one trajectory and return the result. + """ + result = StochasticTrajResult(e_ops, self.options, m_ops=self.m_ops) + generator = self._get_generator(seed) + self._integrator.set_state(tlist[0], state, generator) + state_t = self._restore_state(state, copy=False) + result.add(tlist[0], state_t, None) + for t in tlist[1:]: + t, state, noise = self._integrator.integrate(t, copy=False) + state_t = self._restore_state(state, copy=False) + result.add(t, state_t, noise) + return seed, result + + @classmethod + def avail_integrators(cls): + if cls is StochasticSolver: + return cls._avail_integrators.copy() + return { + **StochasticSolver.avail_integrators(), + **cls._avail_integrators, + } + + +class SMESolver(StochasticSolver): + name = "smesolve" resultclass = StochasticResult _avail_integrators = {} solver_options = { @@ -108,34 +153,29 @@ class SMESolver(MultiTrajSolver): "heterodyne": False, } - def __init__(self, H, sc_ops, *, options=None): + def __init__(self, H, sc_ops, *, options=None, m_ops=()): + self._options = self.solver_options.copy() + self.options = options if isinstance(sc_ops, (Qobj, QobjEvo)): sc_ops = [sc_ops] sc_ops = [QobjEvo(c_op) for c_op in sc_ops] if not H.issuper: L = liouvillian(H, sc_ops) else: + if any(not c_op.isoper for c_op in sc_ops): + raise TypeError("sc_ops must be operators") L = H + sum(lindblad_dissipator(c_op) for c_op in sc_ops) rhs = StochasticOpenSystem(L, sc_ops, self.options["heterodyne"]) super().__init__(rhs, options=options) - if self.options["heterodyne"]: - self.m_ops += [ - sc_op + sc_op.dag(), -1j * (sc_op - sc_op.dag()) - for sc_op in sc_ops - ] + + if len(m_ops) == rhs.num_collapse: + self.m_ops = m_ops + elif self.options["heterodyne"]: + self.m_ops = [] + for sc_op in sc_ops: + self.m_ops += [ + sc_op + sc_op.dag(), -1j * (sc_op - sc_op.dag()) + ] else: self.m_ops = [sc_op + sc_op.dag() for sc_op in sc_ops] - - def _run_one_traj(self, seed, state, tlist, e_ops): - """ - Run one trajectory and return the result. - """ - result = StochasticTrajResult(e_ops, self.options, m_ops=self.m_ops) - generator = self._get_generator(seed) - self._integrator.set_state(tlist[0], state, generator) - result.add(tlist[0], self._restore_state(state, copy=False), None) - for t in tlist[1:]: - t, state, noise = self._integrator.integrate(t, copy=False) - result.add(t, self._restore_state(state, copy=False), noise) - return seed, result From b4120f3f544b99c74fb54146d093a9d457abd418 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 19 Jan 2023 17:04:28 -0500 Subject: [PATCH 025/602] add explicit 1.5 --- qutip/solver/integrator/integrator.py | 82 -------------- qutip/solver/sode/_sode.pyx | 125 ++++++++++++++++++++- qutip/solver/sode/sode.py | 153 ++++++++++++++++++++++++-- qutip/solver/sode/ssystem.pyx | 26 +++-- 4 files changed, 285 insertions(+), 101 deletions(-) diff --git a/qutip/solver/integrator/integrator.py b/qutip/solver/integrator/integrator.py index 23ab8b0039..44df5546f1 100644 --- a/qutip/solver/integrator/integrator.py +++ b/qutip/solver/integrator/integrator.py @@ -238,85 +238,3 @@ def options(self, new_options): if key in new_options and new_options[key] is not None } } - - -class SIntegrator(Integrator): - """ - A wrapper around stochastic ODE solvers. - - Parameters - ---------- - system: qutip.StochasticSystem - Quantum system in which states evolve. - - options: dict - Options for the integrator. - - Class Attributes - ---------------- - name : str - The name of the integrator. - - supports_blackbox : bool - If True, then the integrator calls only ``system.matmul``, - ``system.matmul_data``, ``system.expect``, ``system.expect_data`` and - ``isconstant``, ``isoper`` or ``issuper``. This allows the solver using - the integrator to modify the system in creative ways. In particular, - the solver may modify the system depending on *both* the time ``t`` - *and* the current ``state`` the system is being applied to. - - If the integrator calls any other methods, set to False. - - supports_time_dependent : bool - If True, then the integrator supports time dependent systems. If False, - ``supports_blackbox`` should usually be ``False`` too. - - integrator_options : dict - A dictionary of options used by the integrator and their default - values. Once initiated, ``self.options`` will be a dict with the same - keys, not the full options object passed to the solver. Options' keys - included here will be supported by the :cls:SolverOdeOptions. - """ - def set_state(self, t, state0, generator): - """ - Set the state of the SODE solver. - - Parameters - ---------- - t : float - Initial time - - state0 : qutip.Data - Initial state. - - generator : numpy.random.generator - Random number generator. - """ - raise NotImplementedError - - - def integrate(self, t, copy=True): - """ - Evolve to t. - - Before calling `integrate` for the first time, the initial state should - be set with `set_state`. - - Parameters - ---------- - t : float - Time to integrate to, should be larger than the previous time. - - copy : bool [True] - Whether to return a copy of the state or the state itself. - - Returns - ------- - (t, state, noise) : (float, qutip.Data, np.ndarray) - The state of the solver at ``t``. - """ - raise NotImplementedError - - - def mcstep(self, t, copy=True): - raise NotImplementedError diff --git a/qutip/solver/sode/_sode.pyx b/qutip/solver/sode/_sode.pyx index 92afe83927..4a8becdaed 100644 --- a/qutip/solver/sode/_sode.pyx +++ b/qutip/solver/sode/_sode.pyx @@ -6,8 +6,6 @@ from collections import defaultdict cimport cython import numpy as np -N_dws = defaultdict(lambda : 1) - @cython.boundscheck(False) @cython.wraparound(False) @@ -20,6 +18,7 @@ cpdef Data euler(system, t, Data state, double dt, double[:, :] dW): By Peter E. Kloeden, Eckhard Platen """ cdef int i + a = system.drift(t, state) b = system.diffusion(t, state) new_state = _data.add(state, a, dt) @@ -47,8 +46,10 @@ cpdef Data platen(system, t, Data state, double dt, double[:, :] dW): cdef double sqrt_dt_inv = 0.25 / sqrt_dt cdef double dw, dw2 - d1 = _data.add(state, system.drift(t, state), dt) - d2 = system.diffusion(t, state) + cdef Data d1 = _data.add(state, system.drift(t, state), dt) + cdef list d2 = system.diffusion(t, state) + cdef Data Vt, out + cdef list Vp, Vm out = _data.mul(d1, 0.5) Vt = d1.copy() @@ -76,3 +77,119 @@ cpdef Data platen(system, t, Data state, double dt, double[:, :] dW): out = _data.add(out, d2m[j], -dw2) return out + + +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.cdivision(True) +cdef void explicit15(system, t, Data state, double dt, double[:, :] dW): + """ + Chapter 11.2 Eq. (2.13) + Numerical Solution of Stochastic Differential Equations + By Peter E. Kloeden, Eckhard Platen + """ + cdef int i, j, k + cdef double sqrt_dt = np.sqrt(dt) + cdef double sqrt_dt_inv = 1./sqrt_dt + cdef double ddz, ddw, ddd + cdef double[::1] dz, dw + dw = np.empty(system.num_collapse) + dz = np.empty(system.num_collapse) + for i in range(system.num_collapse): + dw[i] = dW[i, 0] + dz[i] = 0.5 *(dW[i, 0] + 1./np.sqrt(3) * noise[i, 1]) + + d1 = system.drift(t, state) + d2 = system.diffusion(t, state) + dd2 = system.diffusion(t+dt, state) + # Euler part + out = _data.add(state, d1) + for i in range(system.num_collapse): + out = _data.add(out, d2[:], dw[i]) + + V = _data.add(state, d1, 1./self.num_ops) + + v2p = [] + v2m = [] + for i in range(system.num_collapse): + v2p.append(_data.add(V, d2[i], sqrt_dt)) + v2m.append(_data.add(V, d2[i], -sqrt_dt)) + + p2p = [] + p2m = [] + for i in range(system.num_collapse): + d2p = system.diffusion(t, v2p[i]) + d2m = system.diffusion(t, v2m[i]) + ddw = (dw[i] * dw[i] - dt) * 0.25 * sqrt_dt_inv # 1.0 + out = _data.add(out, d2p[i], ddw) + out = _data.add(out, d2m[i], -ddw) + temp_p2p = [] + temp_p2m = [] + for j in range(system.num_collapse): + temp_p2p.append(v2p[i], d2p[j], sqrt_dt) + temp_p2m.append(v2p[i], d2p[j], -sqrt_dt) + p2p.append(temp_p2p) + p2m.append(temp_p2m) + + out = _data.add(out, d1, -0.5*(self.num_ops)) + + for i in range(system.num_collapse): + ddz = dz[i] * 0.5 / sqrt_dt # 1.5 + ddd = 0.25 * (dw[i] * dw[i] / 3 - dt) * dw[i] / dt # 1.5 + + d1p = system.drift(t + dt/self.num_ops, v2p[i]) + d1m = system.drift(t + dt/self.num_ops, v2m[i]) + d2p = system.diffusion(t, v2p[i]) + d2m = system.diffusion(t, v2m[i]) + d2pp = system.diffusion(t, p2p[i][i]) + d2mm = system.diffusion(t, p2m[i][i]) + + out = _data.add(out, d1p, 0.25 + ddz) + out = _data.add(out, d1m, 0.25 - ddz) + + out = _data.add(out, dd2[i], dw[i] - dz[i]) + out = _data.add(out, d2[i], dz[i] - dw[i]) + + out = _data.add(out, d2pp[i], ddd) + out = _data.add(out, d2mm[i], ddd) + out = _data.add(out, d2p[i], ddd) + out = _data.add(out, d2m[i], ddd) + + for j in range(system.num_collapse): + ddw = 0.5 * (dw[j] - dz[j]) # O(1.5) + out = _data.add(out, d2p[j], ddw) + out = _data.add(out, d2[j], -2*ddw) + out = _data.add(out, d2m[j], ddw) + + if j > i: + ddw = 0.5 * (dw[i] * dw[j]) / sqrt_dt # O(1.0) + out = _data.add(out, d2p[j], ddw) + out = _data.add(out, d2m[j], -ddw) + + ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt # O(1.5) + d2pp = self.diffusion(t, p2p[j][i]) + d2mm = self.diffusion(t, p2m[j][i]) + + out = _data.add(out, d2pp[j], ddw) + out = _data.add(out, d2mm[j], -ddw) + out = _data.add(out, d2p[j], -ddw) + out = _data.add(out, d2m[j], ddw)s + + for k in range(j+1, system.num_collapse): + ddw = 0.5 * dw[i] * dw[j] * dw[k] / dt # O(1.5) + + out = _data.add(out, d2pp[k], ddw) + out = _data.add(out, d2mm[k], -ddw) + out = _data.add(out, d2p[k], -ddw) + out = _data.add(out, d2m[k], ddw) + + if j < i: + ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt # O(1.5) + + d2pp = self.diffusion(t, p2p[j][i]) + d2mm = self.diffusion(t, p2m[j][i]) + + out = _data.add(out, d2pp[j], ddw) + out = _data.add(out, d2mm[j], -ddw) + out = _data.add(out, d2p[j], -ddw) + out = _data.add(out, d2m[j], ddw) diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 2161ce386b..9cf25c9370 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -1,9 +1,92 @@ import numpy as np -from . import _sode as sstepper -from ..integrator.integrator import SIntegrator, Integrator +from . import _sode +from ..integrator.integrator import Integrator from ..stochastic import StochasticSolver -class ExplicitIntegrator(SIntegrator): + +class SIntegrator(Integrator): + """ + A wrapper around stochastic ODE solvers. + + Parameters + ---------- + system: qutip.StochasticSystem + Quantum system in which states evolve. + + options: dict + Options for the integrator. + + Class Attributes + ---------------- + name : str + The name of the integrator. + + supports_blackbox : bool + If True, then the integrator calls only ``system.matmul``, + ``system.matmul_data``, ``system.expect``, ``system.expect_data`` and + ``isconstant``, ``isoper`` or ``issuper``. This allows the solver using + the integrator to modify the system in creative ways. In particular, + the solver may modify the system depending on *both* the time ``t`` + *and* the current ``state`` the system is being applied to. + + If the integrator calls any other methods, set to False. + + supports_time_dependent : bool + If True, then the integrator supports time dependent systems. If False, + ``supports_blackbox`` should usually be ``False`` too. + + integrator_options : dict + A dictionary of options used by the integrator and their default + values. Once initiated, ``self.options`` will be a dict with the same + keys, not the full options object passed to the solver. Options' keys + included here will be supported by the :cls:SolverOdeOptions. + """ + def set_state(self, t, state0, generator): + """ + Set the state of the SODE solver. + + Parameters + ---------- + t : float + Initial time + + state0 : qutip.Data + Initial state. + + generator : numpy.random.generator + Random number generator. + """ + raise NotImplementedError + + + def integrate(self, t, copy=True): + """ + Evolve to t. + + Before calling `integrate` for the first time, the initial state should + be set with `set_state`. + + Parameters + ---------- + t : float + Time to integrate to, should be larger than the previous time. + + copy : bool [True] + Whether to return a copy of the state or the state itself. + + Returns + ------- + (t, state, noise) : (float, qutip.Data, np.ndarray) + The state of the solver at ``t``. + """ + raise NotImplementedError + + + def mcstep(self, t, copy=True): + raise NotImplementedError + + +class _Explicit_Simple_Integrator(SIntegrator): """ Stochastic evolution solver """ @@ -11,14 +94,15 @@ class ExplicitIntegrator(SIntegrator): "dt": 0.001, "tol": 1e-7, } + stepper = None + N_dw = 0 + def __init__(self, system, options): self.system = system self._options = self.integrator_options.copy() self.options = options self.dt = self.options["dt"] self.tol = self.options["tol"] - self.stepper = getattr(sstepper, options["method"]) - self.N_dw = sstepper.N_dws[options["method"]] self.N_drift = system.num_collapse def _step(self, dt, dW): @@ -62,7 +146,7 @@ def get_state(self, copy=True): @property def options(self): """ - Supported options by verner method: + Supported options by Explicit Stochastic Integrators: dt : float, default=0.001 Internal time step. @@ -76,6 +160,59 @@ def options(self): def options(self, new_options): Integrator.options.fset(self, new_options) + def __init__(self, system, options): + self.system = system + self._options = self.integrator_options.copy() + self.options = options + self.dt = self.options["dt"] + self.tol = self.options["tol"] + self.stepper = getattr(sstepper, options["method"]) + self.N_dw = sstepper.N_dws[options["method"]] + self.N_drift = system.num_collapse + + +class EulerSODE(_Explicit_Simple_Integrator): + """ + A simple generalization of the Euler method for ordinary + differential equations to stochastic differential equations. Only + solver which could take non-commuting ``sc_ops``. + + - Order: 0.5 + """ + stepper = _sode.euler + N_dw = 1 + + +class PlatenSODE(_Explicit_Simple_Integrator): + """ + Explicit scheme, creates the Milstein using finite differences + instead of analytic derivatives. Also contains some higher order + terms, thus converges better than Milstein while staying strong + order 1.0. Does not require derivatives. See eq. (7.47) of chapter 7 of + H.-P. Breuer and F. Petruccione, *The Theory of Open Quantum Systems*. + + - Order: strong 1, weak 2 + """ + stepper = _sode.platen + N_dw = 1 + + +class Explicit1_5_SODE(_Explicit_Simple_Integrator): + """ + Explicit order 1.5 strong schemes. Reproduce the order 1.5 strong + Taylor scheme using finite difference instead of derivatives. + Slower than ``taylor15`` but usable when derrivatives cannot be + analytically obtained. + See eq. (2.13) of chapter 11.2 of Peter E. Kloeden and Exkhard Platen, + *Numerical Solution of Stochastic Differential Equations.* + + - Order: strong 1.5 + """ + stepper = _sode.explicit15 + N_dw = 2 + -StochasticSolver.add_integrator(ExplicitIntegrator, "euler") -StochasticSolver.add_integrator(ExplicitIntegrator, "platen") +StochasticSolver.add_integrator(EulerSODE, "euler") +StochasticSolver.add_integrator(EulerSODE, "euler-maruyama") +StochasticSolver.add_integrator(PlatenSODE, "platen") +StochasticSolver.add_integrator(PlatenSODE, "explicit1.5") diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index de7546b4f9..5df317f417 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -15,14 +15,13 @@ __all__ = [ ] - -cdef class StochasticSystem: - cdef object d1, d2 +cdef class _StochasticSystem: cdef readonly int num_collapse - cdef double t - cdef double state cdef readonly bint issuper cdef readonly object dims + cdef Data state + cdef double t + cdef object imp def __init__(self, a, b): self.d1 = a @@ -40,11 +39,25 @@ cdef class StochasticSystem: self.state = state +cdef class GeneralStochasticSystem(_StochasticSystem): + cdef object d1, d2 + + def __init__(self, a, b): + self.d1 = a + self.d2 = b + self.num_collapse = 1 + + def drift(self, t, state): + return self.d1(t, state) + + def diffusion(self, t, state): + return self.d2(t, state) + + cdef class StochasticClosedSystem(StochasticSystem): cdef QobjEvo H cdef list c_ops cdef list cpcd_ops - cdef object imp def __init__(self, H, c_ops, heterodyne, implicit=False): self.H = H @@ -84,7 +97,6 @@ cdef class StochasticClosedSystem(StochasticSystem): cdef class StochasticOpenSystem(StochasticSystem): cdef QobjEvo L cdef list c_ops - cdef object imp def __init__(self, H, c_ops, heterodyne): self.L = H From be091be2d00bbf020cd04ba3e3c7b016024a7283 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 20 Jan 2023 16:58:49 -0500 Subject: [PATCH 026/602] debug platen1.5 --- qutip/solve/stochastic.py | 2 +- qutip/solver/sode/_sode.pyx | 82 ++++++++++--------- qutip/solver/sode/sode.py | 12 +-- qutip/solver/sode/ssystem.pyx | 13 +-- qutip/solver/stochastic.py | 147 +++++++++++++++++++++++----------- setup.py | 9 ++- 6 files changed, 161 insertions(+), 104 deletions(-) diff --git a/qutip/solve/stochastic.py b/qutip/solve/stochastic.py index d1f76e685a..dde9876911 100644 --- a/qutip/solve/stochastic.py +++ b/qutip/solve/stochastic.py @@ -121,7 +121,7 @@ def stochastic_solvers(): - Order strong 2.0 - Code: ``'taylor2.0'``, ``'taylor20'`` or ``2.0`` - All solvers, except taylor2.0, are usable in both smesolve and ssesolve + All solvers, except taylor2.0, are usable in both smesolve and (ssesolve) and for both heterodyne and homodyne. taylor2.0 only works for 1 stochastic operator independent of time with the homodyne method. :func:`~general_stochastic` only accepts the derivative-free diff --git a/qutip/solver/sode/_sode.pyx b/qutip/solver/sode/_sode.pyx index 4a8becdaed..a1fae73183 100644 --- a/qutip/solver/sode/_sode.pyx +++ b/qutip/solver/sode/_sode.pyx @@ -41,7 +41,7 @@ cpdef Data platen(system, t, Data state, double dt, double[:, :] dW): The Theory of Open Quantum Systems Chapter 7 Eq. (7.47), H.-P Breuer, F. Petruccione """ - cdef int i, j + cdef int i, j, num_ops = system.num_collapse cdef double sqrt_dt = np.sqrt(dt) cdef double sqrt_dt_inv = 0.25 / sqrt_dt cdef double dw, dw2 @@ -55,7 +55,7 @@ cpdef Data platen(system, t, Data state, double dt, double[:, :] dW): Vt = d1.copy() Vp = [] Vm = [] - for i in range(system.num_collapse): + for i in range(num_ops): Vp.append(_data.add(d1, d2[i], sqrt_dt)) Vm.append(_data.add(d1, d2[i], -sqrt_dt)) Vt = _data.add(Vt, d2[i], dW[i, 0]) @@ -63,7 +63,7 @@ cpdef Data platen(system, t, Data state, double dt, double[:, :] dW): d1 = system.drift(t, Vt) out = _data.add(out, d1, 0.5 * dt) out = _data.add(out, state, 0.5) - for i in range(system.num_collapse): + for i in range(num_ops): d2p = system.diffusion(t, Vp[i]) d2m = system.diffusion(t, Vm[i]) dw = dW[i, 0] * 0.25 @@ -71,7 +71,7 @@ cpdef Data platen(system, t, Data state, double dt, double[:, :] dW): out = _data.add(out, d2[i], 2 * dw) out = _data.add(out, d2p[i], dw) - for j in range(system.num_collapse): + for j in range(num_ops): dw2 = sqrt_dt_inv * (dW[i, 0] * dW[j, 0] - dt * (i == j)) out = _data.add(out, d2p[j], dw2) out = _data.add(out, d2m[j], -dw2) @@ -82,42 +82,42 @@ cpdef Data platen(system, t, Data state, double dt, double[:, :] dW): @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) -cdef void explicit15(system, t, Data state, double dt, double[:, :] dW): +cpdef Data explicit15(system, t, Data state, double dt, double[:, :] dW): """ Chapter 11.2 Eq. (2.13) Numerical Solution of Stochastic Differential Equations By Peter E. Kloeden, Eckhard Platen """ - cdef int i, j, k + cdef int i, j, k, num_ops = system.num_collapse cdef double sqrt_dt = np.sqrt(dt) cdef double sqrt_dt_inv = 1./sqrt_dt cdef double ddz, ddw, ddd cdef double[::1] dz, dw - dw = np.empty(system.num_collapse) - dz = np.empty(system.num_collapse) - for i in range(system.num_collapse): + dw = np.empty(num_ops) + dz = np.empty(num_ops) + for i in range(num_ops): dw[i] = dW[i, 0] - dz[i] = 0.5 *(dW[i, 0] + 1./np.sqrt(3) * noise[i, 1]) + dz[i] = 0.5 *(dW[i, 0] + 1./np.sqrt(3) * dW[i, 1]) d1 = system.drift(t, state) d2 = system.diffusion(t, state) - dd2 = system.diffusion(t+dt, state) + dd2 = system.diffusion(t + dt, state) # Euler part - out = _data.add(state, d1) - for i in range(system.num_collapse): - out = _data.add(out, d2[:], dw[i]) + out = _data.add(state, d1, dt) + for i in range(num_ops): + out = _data.add(out, d2[i], dw[i]) - V = _data.add(state, d1, 1./self.num_ops) + V = _data.add(state, d1, 1./num_ops) v2p = [] v2m = [] - for i in range(system.num_collapse): + for i in range(num_ops): v2p.append(_data.add(V, d2[i], sqrt_dt)) v2m.append(_data.add(V, d2[i], -sqrt_dt)) p2p = [] p2m = [] - for i in range(system.num_collapse): + for i in range(num_ops): d2p = system.diffusion(t, v2p[i]) d2m = system.diffusion(t, v2m[i]) ddw = (dw[i] * dw[i] - dt) * 0.25 * sqrt_dt_inv # 1.0 @@ -125,20 +125,21 @@ cdef void explicit15(system, t, Data state, double dt, double[:, :] dW): out = _data.add(out, d2m[i], -ddw) temp_p2p = [] temp_p2m = [] - for j in range(system.num_collapse): - temp_p2p.append(v2p[i], d2p[j], sqrt_dt) - temp_p2m.append(v2p[i], d2p[j], -sqrt_dt) + for j in range(num_ops): + temp_p2p.append(_data.add(v2p[i], d2p[j], sqrt_dt)) + temp_p2m.append(_data.add(v2p[i], d2p[j], -sqrt_dt)) p2p.append(temp_p2p) p2m.append(temp_p2m) - out = _data.add(out, d1, -0.5*(self.num_ops)) + out = _data.add(out, d1, -0.5*(num_ops)) - for i in range(system.num_collapse): - ddz = dz[i] * 0.5 / sqrt_dt # 1.5 - ddd = 0.25 * (dw[i] * dw[i] / 3 - dt) * dw[i] / dt # 1.5 + for i in range(num_ops): + ddz = dz[i] * 0.5 / sqrt_dt *0 # 1.5 + ddd = 0.25 * (dw[i] * dw[i] / 3 - dt) * dw[i] / dt *0 # 1.5 + + d1p = system.drift(t + dt/num_ops, v2p[i]) + d1m = system.drift(t + dt/num_ops, v2m[i]) - d1p = system.drift(t + dt/self.num_ops, v2p[i]) - d1m = system.drift(t + dt/self.num_ops, v2m[i]) d2p = system.diffusion(t, v2p[i]) d2m = system.diffusion(t, v2m[i]) d2pp = system.diffusion(t, p2p[i][i]) @@ -151,12 +152,13 @@ cdef void explicit15(system, t, Data state, double dt, double[:, :] dW): out = _data.add(out, d2[i], dz[i] - dw[i]) out = _data.add(out, d2pp[i], ddd) - out = _data.add(out, d2mm[i], ddd) - out = _data.add(out, d2p[i], ddd) + out = _data.add(out, d2mm[i], -ddd) + out = _data.add(out, d2p[i], -ddd) out = _data.add(out, d2m[i], ddd) - for j in range(system.num_collapse): - ddw = 0.5 * (dw[j] - dz[j]) # O(1.5) + + for j in range(num_ops): + ddw = 0.5 * (dw[j] - dz[j]) * 0 # O(1.5) out = _data.add(out, d2p[j], ddw) out = _data.add(out, d2[j], -2*ddw) out = _data.add(out, d2m[j], ddw) @@ -166,17 +168,17 @@ cdef void explicit15(system, t, Data state, double dt, double[:, :] dW): out = _data.add(out, d2p[j], ddw) out = _data.add(out, d2m[j], -ddw) - ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt # O(1.5) - d2pp = self.diffusion(t, p2p[j][i]) - d2mm = self.diffusion(t, p2m[j][i]) + ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt * 0 # O(1.5) + d2pp = system.diffusion(t, p2p[j][i]) + d2mm = system.diffusion(t, p2m[j][i]) out = _data.add(out, d2pp[j], ddw) out = _data.add(out, d2mm[j], -ddw) out = _data.add(out, d2p[j], -ddw) - out = _data.add(out, d2m[j], ddw)s + out = _data.add(out, d2m[j], ddw) - for k in range(j+1, system.num_collapse): - ddw = 0.5 * dw[i] * dw[j] * dw[k] / dt # O(1.5) + for k in range(j+1, num_ops): + ddw = 0.5 * dw[i] * dw[j] * dw[k] / dt * 0 # O(1.5) out = _data.add(out, d2pp[k], ddw) out = _data.add(out, d2mm[k], -ddw) @@ -184,12 +186,14 @@ cdef void explicit15(system, t, Data state, double dt, double[:, :] dW): out = _data.add(out, d2m[k], ddw) if j < i: - ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt # O(1.5) + ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt * 0 # O(1.5) - d2pp = self.diffusion(t, p2p[j][i]) - d2mm = self.diffusion(t, p2m[j][i]) + d2pp = system.diffusion(t, p2p[j][i]) + d2mm = system.diffusion(t, p2m[j][i]) out = _data.add(out, d2pp[j], ddw) out = _data.add(out, d2mm[j], -ddw) out = _data.add(out, d2p[j], -ddw) out = _data.add(out, d2m[j], ddw) + + return out diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 9cf25c9370..4cbe348ff8 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -160,16 +160,6 @@ def options(self): def options(self, new_options): Integrator.options.fset(self, new_options) - def __init__(self, system, options): - self.system = system - self._options = self.integrator_options.copy() - self.options = options - self.dt = self.options["dt"] - self.tol = self.options["tol"] - self.stepper = getattr(sstepper, options["method"]) - self.N_dw = sstepper.N_dws[options["method"]] - self.N_drift = system.num_collapse - class EulerSODE(_Explicit_Simple_Integrator): """ @@ -215,4 +205,4 @@ class Explicit1_5_SODE(_Explicit_Simple_Integrator): StochasticSolver.add_integrator(EulerSODE, "euler") StochasticSolver.add_integrator(EulerSODE, "euler-maruyama") StochasticSolver.add_integrator(PlatenSODE, "platen") -StochasticSolver.add_integrator(PlatenSODE, "explicit1.5") +StochasticSolver.add_integrator(Explicit1_5_SODE, "explicit1.5") diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index 5df317f417..dc1304ee21 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -11,7 +11,7 @@ import numpy as np from qutip.core import spre, spost __all__ = [ - "StochasticSystem", "StochasticOpenSystem", "StochasticClosedSystem" + "GeneralStochasticSystem", "StochasticOpenSystem", "StochasticClosedSystem" ] @@ -54,13 +54,13 @@ cdef class GeneralStochasticSystem(_StochasticSystem): return self.d2(t, state) -cdef class StochasticClosedSystem(StochasticSystem): +cdef class StochasticClosedSystem(_StochasticSystem): cdef QobjEvo H cdef list c_ops cdef list cpcd_ops def __init__(self, H, c_ops, heterodyne, implicit=False): - self.H = H + self.H = -1j*H self.c_ops = c_ops self.cpcd_ops = [op + op.dag() for op in c_ops] self.num_collapse = len(c_ops) @@ -72,7 +72,7 @@ cdef class StochasticClosedSystem(StochasticSystem): cdef QobjEvo c_op cdef Data temp, out - out = self.L.matmul_data(t, state) + out = self.H.matmul_data(t, state) for i in range(self.num_collapse): c_op = self.cpcd_ops[i] e = c_op.expect_data(t, state) @@ -80,6 +80,7 @@ cdef class StochasticClosedSystem(StochasticSystem): temp = c_op.matmul_data(t, state) out = _data.add(out, state, -0.125 * e * e) out = _data.add(out, temp, 0.5 * e) + return out def diffusion(self, double t, Data state): cdef int i @@ -90,11 +91,11 @@ cdef class StochasticClosedSystem(StochasticSystem): _out = c_op.matmul_data(t, state) c_op = self.cpcd_ops[i] expect = c_op.expect_data(t, state) - out.append(_data.add(out, state, -0.5 * expect)) + out.append(_data.add(_out, state, -0.5 * expect)) return out -cdef class StochasticOpenSystem(StochasticSystem): +cdef class StochasticOpenSystem(_StochasticSystem): cdef QobjEvo L cdef list c_ops diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 39ed014674..3effba4333 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -1,4 +1,4 @@ -__all__ = ["smesolve", "SMESolver"] +__all__ = ["smesolve", "SMESolver", "ssesolve", "SSESolver", "StochasticSolver"] from .sode.ssystem import * from .result import MultiTrajResult, Result @@ -81,18 +81,76 @@ def smesolve(H, rho0, tlist, c_ops=(), sc_ops=(), e_ops=(), m_ops=(), output: :class:`qutip.solver.Result` An instance of the class :class:`qutip.solver.Result`. - """ - L = QobjEvo(H, args=args) + H = QobjEvo(H, args=args) c_ops = [QobjEvo(c_op, args=args) for c_op in c_ops] sc_ops = [QobjEvo(c_op, args=args) for c_op in sc_ops] - L = liouvillian(H, c_ops) + if H.issuper: + L = H + liouvillian(None, c_ops) + else: + L = liouvillian(H, c_ops) sol = SMESolver(L, sc_ops, options=options or {}, m_ops=m_ops) return sol.run(rho0, tlist, ntraj, e_ops=e_ops) + +def ssesolve(H, psi0, tlist, sc_ops=(), e_ops=(), m_ops=(), + args={}, ntraj=500, options=None): + """ + Solve stochastic master equation. Dispatch to specific solvers + depending on the value of the `solver` keyword argument. + + Parameters + ---------- + + H : :class:`qutip.Qobj`, or time dependent system. + System Hamiltonian. + Can depend on time, see StochasticSolverOptions help for format. + + rho0 : :class:`qutip.Qobj` + Initial density matrix or state vector (ket). + + tlist : *list* / *array* + List of times for :math:`t`. Must be uniformly spaced. + + c_ops : list of :class:`qutip.Qobj`, or time dependent Qobjs. + Deterministic collapse operator which will contribute with a standard + Lindblad type of dissipation. + Can depend on time, see StochasticSolverOptions help for format. + + sc_ops : list of :class:`qutip.Qobj`, or time dependent Qobjs. + List of stochastic collapse operators. Each stochastic collapse + operator will give a deterministic and stochastic contribution + to the eqaution of motion according to how the d1 and d2 functions + are defined. + Can depend on time, see StochasticSolverOptions help for format. + + e_ops : list of :class:`qutip.Qobj` + Single operator or list of operators for which to evaluate + expectation values. + + m_ops : list of :class:`qutip.Qobj` + Single operator or list of operators for which to evaluate + expectation values. + + args : dict + ... + + Returns + ------- + + output: :class:`qutip.solver.Result` + + An instance of the class :class:`qutip.solver.Result`. + """ + H = QobjEvo(H, args=args) + sc_ops = [QobjEvo(c_op, args=args) for c_op in sc_ops] + sol = SSESolver(H, sc_ops, options=options or {}, m_ops=m_ops) + return sol.run(psi0, tlist, ntraj, e_ops=e_ops) + + class StochasticSolver(MultiTrajSolver): - name = "generic stochastic" + name = "StochasticSolver" resultclass = StochasticResult _avail_integrators = {} solver_options = { @@ -102,13 +160,47 @@ class StochasticSolver(MultiTrajSolver): "store_states": None, "keep_runs_results": False, "normalize_output": False, - "method": "euler", + "method": "platen", "map": "serial", "job_timeout": None, "num_cpus": None, "bitgenerator": None, + "heterodyne": False, } + def __init__(self, H, sc_ops, heterodyne=False, *, options=None, m_ops=()): + self._options = self.solver_options.copy() + self.options = options + + if not isinstance(H, (Qobj, QobjEvo)): + raise TypeError("...") + H = QobjEvo(H) + + if isinstance(sc_ops, (Qobj, QobjEvo)): + sc_ops = [sc_ops] + sc_ops = [QobjEvo(c_op) for c_op in sc_ops] + if any(not c_op.isoper for c_op in sc_ops): + raise TypeError("sc_ops must be operators") + + if H.issuper: + L = H + liouvillian(None, sc_ops) + rhs = StochasticOpenSystem(L, sc_ops, heterodyne) + else: + rhs = StochasticClosedSystem(H, sc_ops, heterodyne) + + super().__init__(rhs, options=options) + + if len(m_ops) == rhs.num_collapse: + self.m_ops = m_ops + elif heterodyne: + self.m_ops = [] + for sc_op in sc_ops: + self.m_ops += [ + sc_op + sc_op.dag(), -1j * (sc_op - sc_op.dag()) + ] + else: + self.m_ops = [sc_op + sc_op.dag() for sc_op in sc_ops] + def _run_one_traj(self, seed, state, tlist, e_ops): """ Run one trajectory and return the result. @@ -136,46 +228,9 @@ def avail_integrators(cls): class SMESolver(StochasticSolver): name = "smesolve" - resultclass = StochasticResult _avail_integrators = {} - solver_options = { - "progress_bar": "text", - "progress_kwargs": {"chunk_size": 10}, - "store_final_state": False, - "store_states": None, - "keep_runs_results": False, - "normalize_output": False, - "method": "platen", - "map": "serial", - "job_timeout": None, - "num_cpus": None, - "bitgenerator": None, - "heterodyne": False, - } - def __init__(self, H, sc_ops, *, options=None, m_ops=()): - self._options = self.solver_options.copy() - self.options = options - if isinstance(sc_ops, (Qobj, QobjEvo)): - sc_ops = [sc_ops] - sc_ops = [QobjEvo(c_op) for c_op in sc_ops] - if not H.issuper: - L = liouvillian(H, sc_ops) - else: - if any(not c_op.isoper for c_op in sc_ops): - raise TypeError("sc_ops must be operators") - L = H + sum(lindblad_dissipator(c_op) for c_op in sc_ops) - rhs = StochasticOpenSystem(L, sc_ops, self.options["heterodyne"]) - super().__init__(rhs, options=options) - - if len(m_ops) == rhs.num_collapse: - self.m_ops = m_ops - elif self.options["heterodyne"]: - self.m_ops = [] - for sc_op in sc_ops: - self.m_ops += [ - sc_op + sc_op.dag(), -1j * (sc_op - sc_op.dag()) - ] - else: - self.m_ops = [sc_op + sc_op.dag() for sc_op in sc_ops] +class SSESolver(StochasticSolver): + name = "ssesolve" + _avail_integrators = {} diff --git a/setup.py b/setup.py index 80e80c4157..51a3061a5f 100755 --- a/setup.py +++ b/setup.py @@ -44,6 +44,13 @@ def process_options(): options = _determine_user_arguments(options) options = _determine_version(options) options = _determine_compilation_options(options) + options["annotate"] = False + if "--annotate" in sys.argv: + options["annotate"] = True + sys.argv.remove("--annotate") + if "-a" in sys.argv: + options["annotate"] = True + sys.argv.remove("-a") return options @@ -269,7 +276,7 @@ def create_extension_modules(options): extra_compile_args=options['cflags'], extra_link_args=options['ldflags'], language='c++')) - return cythonize(out) + return cythonize(out, annotate=options["annotate"]) def print_epilogue(): From 0717e0125ff2a64156adaf1455eaf208896bc0c0 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 23 Jan 2023 17:08:40 -0500 Subject: [PATCH 027/602] Fix a bug in explicit1.5 --- qutip/solve/_stochastic.pyx | 2 +- qutip/solver/sode/_sode.pyx | 26 ++++++++++--------- qutip/solver/sode/ssystem.pxd | 15 +++++++++++ qutip/solver/sode/ssystem.pyx | 47 +++++++++++++++++++---------------- qutip/solver/stochastic.py | 3 +-- 5 files changed, 57 insertions(+), 36 deletions(-) create mode 100644 qutip/solver/sode/ssystem.pxd diff --git a/qutip/solve/_stochastic.pyx b/qutip/solve/_stochastic.pyx index 91799251bf..156fab6466 100644 --- a/qutip/solve/_stochastic.pyx +++ b/qutip/solve/_stochastic.pyx @@ -126,7 +126,7 @@ cdef void _normalize_rho(complex[::1] rho): cdef complex[::1,:] mat = np.reshape(rho, (N,N), order="F") cdef complex[::1,:] eivec cdef double[::1] eival = np.zeros(N) - eivec = _data.eigs(Dense(mat, copy=False), True, True)[1].full() + eivec = _data.eigs(Dense(mat, copy=False), True, True)[1].to_array() _zero(rho) cdef int i, j, k diff --git a/qutip/solver/sode/_sode.pyx b/qutip/solver/sode/_sode.pyx index a1fae73183..a399f107c6 100644 --- a/qutip/solver/sode/_sode.pyx +++ b/qutip/solver/sode/_sode.pyx @@ -1,15 +1,17 @@ #cython: language_level=3 + from qutip.core import data as _data from qutip.core.cy.qobjevo cimport QobjEvo from qutip.core.data cimport Data from collections import defaultdict cimport cython +from qutip.solver.sode.ssystem cimport _StochasticSystem import numpy as np @cython.boundscheck(False) @cython.wraparound(False) -cpdef Data euler(system, t, Data state, double dt, double[:, :] dW): +cpdef Data euler(_StochasticSystem system, t, Data state, double dt, double[:, :] dW): """ Integration scheme: Basic Euler order 0.5 @@ -29,7 +31,7 @@ cpdef Data euler(system, t, Data state, double dt, double[:, :] dW): @cython.boundscheck(False) @cython.wraparound(False) -cpdef Data platen(system, t, Data state, double dt, double[:, :] dW): +cpdef Data platen(_StochasticSystem system, t, Data state, double dt, double[:, :] dW): """ Platen rhs function for both master eq and schrodinger eq. dV = -iH* (V+Vt)/2 * dt + (d1(V)+d1(Vt))/2 * dt @@ -82,7 +84,7 @@ cpdef Data platen(system, t, Data state, double dt, double[:, :] dW): @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) -cpdef Data explicit15(system, t, Data state, double dt, double[:, :] dW): +cpdef Data explicit15(_StochasticSystem system, t, Data state, double dt, double[:, :] dW): """ Chapter 11.2 Eq. (2.13) Numerical Solution of Stochastic Differential Equations @@ -131,11 +133,11 @@ cpdef Data explicit15(system, t, Data state, double dt, double[:, :] dW): p2p.append(temp_p2p) p2m.append(temp_p2m) - out = _data.add(out, d1, -0.5*(num_ops)) + out = _data.add(out, d1, -0.5*(num_ops) * dt) for i in range(num_ops): - ddz = dz[i] * 0.5 / sqrt_dt *0 # 1.5 - ddd = 0.25 * (dw[i] * dw[i] / 3 - dt) * dw[i] / dt *0 # 1.5 + ddz = dz[i] * 0.5 / sqrt_dt # 1.5 + ddd = 0.25 * (dw[i] * dw[i] / 3 - dt) * dw[i] / dt # 1.5 d1p = system.drift(t + dt/num_ops, v2p[i]) d1m = system.drift(t + dt/num_ops, v2m[i]) @@ -145,8 +147,8 @@ cpdef Data explicit15(system, t, Data state, double dt, double[:, :] dW): d2pp = system.diffusion(t, p2p[i][i]) d2mm = system.diffusion(t, p2m[i][i]) - out = _data.add(out, d1p, 0.25 + ddz) - out = _data.add(out, d1m, 0.25 - ddz) + out = _data.add(out, d1p, (0.25 + ddz) * dt) + out = _data.add(out, d1m, (0.25 - ddz) * dt) out = _data.add(out, dd2[i], dw[i] - dz[i]) out = _data.add(out, d2[i], dz[i] - dw[i]) @@ -158,7 +160,7 @@ cpdef Data explicit15(system, t, Data state, double dt, double[:, :] dW): for j in range(num_ops): - ddw = 0.5 * (dw[j] - dz[j]) * 0 # O(1.5) + ddw = 0.5 * (dw[j] - dz[j]) # O(1.5) out = _data.add(out, d2p[j], ddw) out = _data.add(out, d2[j], -2*ddw) out = _data.add(out, d2m[j], ddw) @@ -168,7 +170,7 @@ cpdef Data explicit15(system, t, Data state, double dt, double[:, :] dW): out = _data.add(out, d2p[j], ddw) out = _data.add(out, d2m[j], -ddw) - ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt * 0 # O(1.5) + ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt # O(1.5) d2pp = system.diffusion(t, p2p[j][i]) d2mm = system.diffusion(t, p2m[j][i]) @@ -178,7 +180,7 @@ cpdef Data explicit15(system, t, Data state, double dt, double[:, :] dW): out = _data.add(out, d2m[j], ddw) for k in range(j+1, num_ops): - ddw = 0.5 * dw[i] * dw[j] * dw[k] / dt * 0 # O(1.5) + ddw = 0.5 * dw[i] * dw[j] * dw[k] / dt # O(1.5) out = _data.add(out, d2pp[k], ddw) out = _data.add(out, d2mm[k], -ddw) @@ -186,7 +188,7 @@ cpdef Data explicit15(system, t, Data state, double dt, double[:, :] dW): out = _data.add(out, d2m[k], ddw) if j < i: - ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt * 0 # O(1.5) + ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt # O(1.5) d2pp = system.diffusion(t, p2p[j][i]) d2mm = system.diffusion(t, p2m[j][i]) diff --git a/qutip/solver/sode/ssystem.pxd b/qutip/solver/sode/ssystem.pxd new file mode 100644 index 0000000000..8c7c7e47c5 --- /dev/null +++ b/qutip/solver/sode/ssystem.pxd @@ -0,0 +1,15 @@ +from qutip.core.data cimport Data + +cdef class _StochasticSystem: + cdef readonly int num_collapse + cdef readonly bint issuper + cdef readonly object dims + cdef Data state + cdef double t + cdef object imp + + cpdef Data drift(self, t, Data state) + + cpdef list diffusion(self, t, Data state) + + cdef void set_state(self, double t, Data state) diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index dc1304ee21..777fc1290e 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -8,7 +8,7 @@ from qutip.core.cy.qobjevo cimport QobjEvo from qutip.core.data cimport Data cimport cython import numpy as np -from qutip.core import spre, spost +from qutip.core import spre, spost, liouvillian __all__ = [ "GeneralStochasticSystem", "StochasticOpenSystem", "StochasticClosedSystem" @@ -16,22 +16,16 @@ __all__ = [ cdef class _StochasticSystem: - cdef readonly int num_collapse - cdef readonly bint issuper - cdef readonly object dims - cdef Data state - cdef double t - cdef object imp def __init__(self, a, b): self.d1 = a self.d2 = b self.num_collapse = 1 - def drift(self, t, state): + cpdef Data drift(self, t, Data state): return self.d1(t, state) - def diffusion(self, t, state): + cpdef list diffusion(self, t, Data state): return self.d2(t, state) cdef void set_state(self, double t, Data state): @@ -47,10 +41,10 @@ cdef class GeneralStochasticSystem(_StochasticSystem): self.d2 = b self.num_collapse = 1 - def drift(self, t, state): + cpdef Data drift(self, t, Data state): return self.d1(t, state) - def diffusion(self, t, state): + cpdef list diffusion(self, t, Data state): return self.d2(t, state) @@ -59,15 +53,26 @@ cdef class StochasticClosedSystem(_StochasticSystem): cdef list c_ops cdef list cpcd_ops - def __init__(self, H, c_ops, heterodyne, implicit=False): - self.H = -1j*H - self.c_ops = c_ops - self.cpcd_ops = [op + op.dag() for op in c_ops] - self.num_collapse = len(c_ops) + def __init__(self, H, c_ops, heterodyne): + self.H = -1j * H + if heterodyne: + self.c_ops = [] + for c_op in c_ops: + self.c_ops.append(c_op / np.sqrt(2)) + self.c_ops.append(c_op * (-1j / np.sqrt(2))) + self.cpcd_ops.append((c_op + c_op.dag()) / np.sqrt(2)) + self.cpcd_ops.append((-c_op + c_op.dag()) * 1j / np.sqrt(2)) + else: + self.c_ops = c_ops + self.cpcd_ops = [op + op.dag() for op in c_ops] + + self.num_collapse = len(self.c_ops) + for c_op in self.c_ops: + self.H += -0.5 * c_op.dag() * c_op self.issuper = False self.dims = self.H.dims - def drift(self, double t, Data state): + cpdef Data drift(self, t, Data state): cdef int i cdef QobjEvo c_op cdef Data temp, out @@ -82,7 +87,7 @@ cdef class StochasticClosedSystem(_StochasticSystem): out = _data.add(out, temp, 0.5 * e) return out - def diffusion(self, double t, Data state): + cpdef list diffusion(self, t, Data state): cdef int i cdef QobjEvo c_op out = [] @@ -100,7 +105,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): cdef list c_ops def __init__(self, H, c_ops, heterodyne): - self.L = H + self.L = H + liouvillian(None, c_ops) if heterodyne: self.c_ops = [] for c in c_ops: @@ -114,10 +119,10 @@ cdef class StochasticOpenSystem(_StochasticSystem): self.issuper = True self.dims = self.L.dims - def drift(self, double t, Data state): + cpdef Data drift(self, t, Data state): return self.L.matmul_data(t, state) - def diffusion(self, double t, Data state): + cpdef list diffusion(self, t, Data state): cdef int i cdef QobjEvo c_op cdef complex expect diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 3effba4333..1c7fba6eb6 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -183,8 +183,7 @@ def __init__(self, H, sc_ops, heterodyne=False, *, options=None, m_ops=()): raise TypeError("sc_ops must be operators") if H.issuper: - L = H + liouvillian(None, sc_ops) - rhs = StochasticOpenSystem(L, sc_ops, heterodyne) + rhs = StochasticOpenSystem(H, sc_ops, heterodyne) else: rhs = StochasticClosedSystem(H, sc_ops, heterodyne) From 9a0368ea515b7d3998e3693c732d11ef3cb4e466 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 24 Jan 2023 17:49:42 -0500 Subject: [PATCH 028/602] make debugging tools --- qutip/solve/_stochastic.pyx | 149 ++++++++++++++++++---------------- qutip/solve/stochastic.py | 26 ++++-- qutip/solver/sode/check.py | 11 +++ qutip/solver/sode/ito.py | 34 ++++++++ qutip/solver/sode/ssystem.pxd | 1 + 5 files changed, 141 insertions(+), 80 deletions(-) create mode 100644 qutip/solver/sode/check.py create mode 100644 qutip/solver/sode/ito.py diff --git a/qutip/solve/_stochastic.pyx b/qutip/solve/_stochastic.pyx index 156fab6466..c2cac5416c 100644 --- a/qutip/solve/_stochastic.pyx +++ b/qutip/solve/_stochastic.pyx @@ -25,13 +25,13 @@ cdef int ONE=1 """Some of blas wrapper""" @cython.boundscheck(False) -cdef void _axpy(complex a, complex[::1] x, complex[::1] y): +cpdef void _axpy(complex a, complex[::1] x, complex[::1] y): """ y += a*x""" cdef int l = x.shape[0] zaxpy(&l, &a, &x[0], &ONE, &y[0], &ONE) @cython.boundscheck(False) -cdef void copy(complex[::1] x, complex[::1] y): +cpdef void copy(complex[::1] x, complex[::1] y): """ y = x """ cdef int l = x.shape[0] zcopy(&l, &x[0], &ONE, &y[0], &ONE) @@ -55,37 +55,37 @@ cdef double _dznrm2(complex[::1] vec): return raw_dznrm2(&l, &vec[0], &ONE) @cython.boundscheck(False) -cdef void _scale(double a, complex[::1] x): +cpdef void _scale(double a, complex[::1] x): """ x *= a """ cdef int l = x.shape[0] zdscal(&l, &a, &x[0], &ONE) @cython.boundscheck(False) -cdef void _zscale(complex a, complex[::1] x): +cpdef void _zscale(complex a, complex[::1] x): """ x *= a """ cdef int l = x.shape[0] zscal(&l, &a, &x[0], &ONE) @cython.boundscheck(False) -cdef void _zero(complex[::1] x): +cpdef void _zero(complex[::1] x): """ x *= 0 """ cdef int l = x.shape[0] zdscal(&l, &DZERO, &x[0], &ONE) @cython.boundscheck(False) -cdef void _zero_2d(complex[:,::1] x): +cpdef void _zero_2d(complex[:,::1] x): """ x *= 0 """ cdef int l = x.shape[0]*x.shape[1] zdscal(&l, &DZERO, &x[0,0], &ONE) @cython.boundscheck(False) -cdef void _zero_3d(complex[:,:,::1] x): +cpdef void _zero_3d(complex[:,:,::1] x): """ x *= 0 """ cdef int l = x.shape[0]*x.shape[1]*x.shape[2] zdscal(&l, &DZERO, &x[0,0,0], &ONE) @cython.boundscheck(False) -cdef void _zero_4d(complex[:,:,:,::1] x): +cpdef void _zero_4d(complex[:,:,:,::1] x): """ x *= 0 """ cdef int l = x.shape[0]*x.shape[1]*x.shape[2]*x.shape[3] zdscal(&l, &DZERO, &x[0,0,0,0], &ONE) @@ -99,7 +99,7 @@ cdef Dense _dense_wrap(double complex [::1] x): # functions for ensuring that the states stay physical @cython.cdivision(True) @cython.boundscheck(False) -cdef void _normalize_inplace(complex[::1] vec): +cpdef void _normalize_inplace(complex[::1] vec): """ make norm of vec equal to 1""" cdef int l = vec.shape[0] cdef double norm = 1.0/_dznrm2(vec) @@ -117,7 +117,7 @@ def normalize_inplace(complex[::1] vec): @cython.cdivision(True) @cython.boundscheck(False) -cdef void _normalize_rho(complex[::1] rho): +cpdef void _normalize_rho(complex[::1] rho): """ Ensure that the density matrix trace is one and that the composing states are normalized. """ @@ -299,16 +299,17 @@ cdef class StochasticSolver: d1, d2 and there derivatives up to dt**2.0 one sc_ops """ - cdef int l_vec, num_ops - cdef Solvers solver - cdef int num_step, num_substeps, num_dw - cdef int normalize - cdef double dt + cdef readonly int l_vec, num_ops + cdef readonly Solvers solver + cdef public int num_step, num_substeps + cdef readonly int num_dw + cdef public int normalize + cdef readonly double dt cdef int noise_type cdef object custom_noise cdef double[::1] dW_factor cdef unsigned int[::1] seed - cdef object sso + cdef readonly object sso # buffer to not redo the initialisation at each substep cdef complex[:, ::1] buffer_1d @@ -444,6 +445,10 @@ cdef class StochasticSolver: elif self.noise_type == 0: self.seed = sso.noise + def reset_solver(self, new_solver): + self.sso.solver = new_solver + self.set_solver(self.sso) + def set_data(self, sso): """Set solver specific operator""" pass @@ -606,29 +611,29 @@ cdef class StochasticSolver: self._normalize_inplace(vec) return vec - cdef void _normalize_inplace(self, complex[::1] vec): + cpdef void _normalize_inplace(self, complex[::1] vec): _normalize_inplace(vec) # Dummy functions # Needed for compilation since ssesolve is not stand-alone - cdef void d1(self, double t, complex[::1] v, complex[::1] out): + cpdef void d1(self, double t, complex[::1] v, complex[::1] out): """ deterministic part of the evolution depend on schrodinger vs master vs photocurrent """ pass - cdef void d2(self, double t, complex[::1] v, complex[:, ::1] out): + cpdef void d2(self, double t, complex[::1] v, complex[:, ::1] out): """ stochastic part of the evolution depend on schrodinger vs master vs photocurrent """ pass - cdef void implicit(self, double t, np.ndarray[complex, ndim=1] dvec, + cpdef void implicit(self, double t, np.ndarray[complex, ndim=1] dvec, complex[::1] out, np.ndarray[complex, ndim=1] guess) except *: """ Do the step X(t+dt) = f(X(t+dt)) + g(X(t)) """ pass - cdef void derivatives(self, double t, int deg, complex[::1] rho, + cpdef void derivatives(self, double t, int deg, complex[::1] rho, complex[::1] a, complex[:, ::1] b, complex[:, :, ::1] Lb, complex[:,::1] La, complex[:, ::1] L0b, complex[:, :, :, ::1] LLb, @@ -639,7 +644,7 @@ cdef class StochasticSolver: """ pass - cdef void derivativesO2(self, double t, complex[::1] rho, + cpdef void derivativesO2(self, double t, complex[::1] rho, complex[::1] a, complex[::1] b, complex[::1] Lb, complex[::1] La, complex[::1] L0b, complex[::1] LLb, complex[::1] L0a, @@ -651,21 +656,21 @@ cdef class StochasticSolver: """ pass - cdef void photocurrent(self, double t, double dt, double[:] noise, + cpdef void photocurrent(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): """Special integration scheme: photocurrent collapse + euler evolution """ pass - cdef void photocurrent_pc(self, double t, double dt, double[:] noise, + cpdef void photocurrent_pc(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): """Special integration scheme: photocurrent collapse + predictor-corrector evolution """ pass - cdef void rouchon(self, double t, double dt, double[:] noise, + cpdef void rouchon(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): """Special integration scheme: Force valid density matrix using positive map @@ -676,7 +681,7 @@ cdef class StochasticSolver: @cython.boundscheck(False) @cython.wraparound(False) - cdef void euler(self, double t, double dt, double[:] noise, + cpdef void euler(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): """Integration scheme: Basic Euler order 0.5 @@ -696,7 +701,7 @@ cdef class StochasticSolver: @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef void platen(self, double t, double dt, double[:] noise, + cpdef void platen(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): """ Platen rhs function for both master eq and schrodinger eq. @@ -759,7 +764,7 @@ cdef class StochasticSolver: @cython.wraparound(False) @cython.boundscheck(False) @cython.cdivision(True) - cdef void pred_corr(self, double t, double dt, double[:] noise, + cpdef void pred_corr(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): """ Chapter 15.5 Eq. (5.4) @@ -795,7 +800,7 @@ cdef class StochasticSolver: @cython.wraparound(False) @cython.boundscheck(False) @cython.cdivision(True) - cdef void pred_corr_a(self, double t, double dt, double[:] noise, + cpdef void pred_corr_a(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): """ Chapter 15.5 Eq. (5.4) @@ -838,7 +843,7 @@ cdef class StochasticSolver: @cython.wraparound(False) @cython.boundscheck(False) @cython.cdivision(True) - cdef void milstein(self, double t, double dt, double[:] noise, + cpdef void milstein(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): """ Chapter 10.3 Eq. (3.1) @@ -869,7 +874,7 @@ cdef class StochasticSolver: @cython.wraparound(False) @cython.boundscheck(False) @cython.cdivision(True) - cdef void milstein_imp(self, double t, double dt, double[:] noise, + cpdef void milstein_imp(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out) except *: """ Chapter 12.2 Eq. (2.9) @@ -907,7 +912,7 @@ cdef class StochasticSolver: @cython.wraparound(False) @cython.boundscheck(False) @cython.cdivision(True) - cdef void taylor15(self, double t, double dt, double[:] noise, + cpdef void taylor15(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): """ Chapter 12.2 Eq. (2.18), @@ -959,7 +964,7 @@ cdef class StochasticSolver: @cython.wraparound(False) @cython.boundscheck(False) @cython.cdivision(True) - cdef void taylor15_imp(self, double t, double dt, double[:] noise, + cpdef void taylor15_imp(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out) except *: """ Chapter 12.2 Eq. (2.18), @@ -1017,7 +1022,7 @@ cdef class StochasticSolver: @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef void platen15(self, double t, double dt, double[:] noise, + cpdef void platen15(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): """ Chapter 11.2 Eq. (2.13) @@ -1160,7 +1165,7 @@ cdef class StochasticSolver: @cython.wraparound(False) @cython.boundscheck(False) @cython.cdivision(True) - cdef void taylor20(self, double t, double dt, double[::1] noise, + cpdef void taylor20(self, double t, double dt, double[::1] noise, complex[::1] vec, complex[::1] out): """ Chapter 10.5 Eq. (5.1), @@ -1217,11 +1222,11 @@ cdef class StochasticSolver: cdef class SSESolver(StochasticSolver): """stochastic Schrodinger system""" - cdef QobjEvo L - cdef object c_ops - cdef object cpcd_ops - cdef object imp - cdef double tol, imp_t + cdef readonly QobjEvo L + cdef readonly object c_ops + cdef readonly object cpcd_ops + cdef readonly object imp + cdef readonly double tol, imp_t def set_data(self, sso): L = sso.LH @@ -1250,7 +1255,7 @@ cdef class SSESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef void d1(self, double t, complex[::1] vec, complex[::1] out): + cpdef void d1(self, double t, complex[::1] vec, complex[::1] out): self.L.matmul_data(t, _dense_wrap(vec), out=_dense_wrap(out)) cdef int i cdef complex e @@ -1269,7 +1274,7 @@ cdef class SSESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef void d2(self, double t, complex[::1] vec, complex[:, ::1] out): + cpdef void d2(self, double t, complex[::1] vec, complex[:, ::1] out): cdef int i, k cdef QobjEvo c_op cdef complex expect @@ -1283,7 +1288,7 @@ cdef class SSESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef void derivatives(self, double t, int deg, complex[::1] vec, + cpdef void derivatives(self, double t, int deg, complex[::1] vec, complex[::1] a, complex[:, ::1] b, complex[:, :, ::1] Lb, complex[:,::1] La, complex[:, ::1] L0b, complex[:, :, :, ::1] LLb, @@ -1432,7 +1437,7 @@ cdef class SSESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) - cdef void _c_vec_conj(self, double t, QobjEvo c_op, + cpdef void _c_vec_conj(self, double t, QobjEvo c_op, complex[::1] vec, complex[::1] out): cdef int k cdef complex[::1] temp = self.func_buffer_1d[13,:] @@ -1444,7 +1449,7 @@ cdef class SSESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef void derivativesO2(self, double t, complex[::1] psi, + cpdef void derivativesO2(self, double t, complex[::1] psi, complex[::1] a, complex[::1] b, complex[::1] Lb, complex[::1] La, complex[::1] L0b, complex[::1] LLb, complex[::1] L0a, @@ -1612,7 +1617,7 @@ cdef class SSESolver(StochasticSolver): _axpy(de_a * dt, Cpsi, L0a) # a'_a_ _axpy(-0.5 * dt, L0Lb, L0a) # _L0_Lb/2 - cdef void implicit(self, double t, np.ndarray[complex, ndim=1] dvec, + cpdef void implicit(self, double t, np.ndarray[complex, ndim=1] dvec, complex[::1] out, np.ndarray[complex, ndim=1] guess) except *: # np.ndarray to memoryview is OK but not the reverse @@ -1626,11 +1631,11 @@ cdef class SSESolver(StochasticSolver): cdef class SMESolver(StochasticSolver): """stochastic master equation system""" - cdef QobjEvo L - cdef object imp - cdef object c_ops - cdef int N_root - cdef double tol + cdef readonly QobjEvo L + cdef readonly object imp + cdef readonly object c_ops + cdef readonly int N_root + cdef readonly double tol def set_data(self, sso): L = sso.LH @@ -1646,7 +1651,7 @@ cdef class SMESolver(StochasticSolver): self.tol = sso.tol self.imp = sso.imp - cdef void _normalize_inplace(self, complex[::1] vec): + cpdef void _normalize_inplace(self, complex[::1] vec): _normalize_rho(vec) @cython.boundscheck(False) @@ -1660,13 +1665,13 @@ cdef class SMESolver(StochasticSolver): return e @cython.boundscheck(False) - cdef void d1(self, double t, complex[::1] rho, complex[::1] out): + cpdef void d1(self, double t, complex[::1] rho, complex[::1] out): self.L.matmul_data(t, _dense_wrap(rho), _dense_wrap(out)) @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef void d2(self, double t, complex[::1] rho, complex[:, ::1] out): + cpdef void d2(self, double t, complex[::1] rho, complex[:, ::1] out): cdef int i, k cdef QobjEvo c_op cdef complex expect @@ -1679,7 +1684,7 @@ cdef class SMESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef void derivatives(self, double t, int deg, complex[::1] rho, + cpdef void derivatives(self, double t, int deg, complex[::1] rho, complex[::1] a, complex[:, ::1] b, complex[:, :, ::1] Lb, complex[:,::1] La, complex[:, ::1] L0b, complex[:, :, :, ::1] LLb, @@ -1777,7 +1782,7 @@ cdef class SMESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef void derivativesO2(self, double t, complex[::1] rho, + cpdef void derivativesO2(self, double t, complex[::1] rho, complex[::1] a, complex[::1] b, complex[::1] Lb, complex[::1] La, complex[::1] L0b, complex[::1] LLb, complex[::1] L0a, @@ -1881,7 +1886,7 @@ cdef class SMESolver(StochasticSolver): _axpy(-self.dt*0.5, L0Lb, L0a) - cdef void implicit(self, double t, np.ndarray[complex, ndim=1] dvec, + cpdef void implicit(self, double t, np.ndarray[complex, ndim=1] dvec, complex[::1] out, np.ndarray[complex, ndim=1] guess) except *: # np.ndarray to memoryview is OK but not the reverse @@ -1913,7 +1918,7 @@ cdef class PcSSESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) - cdef void photocurrent(self, double t, double dt, double[:] noise, + cpdef void photocurrent(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): cdef QobjEvo c_op cdef double rand @@ -1942,7 +1947,7 @@ cdef class PcSSESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) - cdef void photocurrent_pc(self, double t, double dt, double[:] noise, + cpdef void photocurrent_pc(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): cdef QobjEvo c_op cdef double expect @@ -2002,7 +2007,7 @@ cdef class PcSSESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef void d1(self, double t, complex[::1] vec, complex[::1] out): + cpdef void d1(self, double t, complex[::1] vec, complex[::1] out): self.L.matmul_data(t, _dense_wrap(vec), _dense_wrap(out)) cdef int i cdef complex e @@ -2018,7 +2023,7 @@ cdef class PcSSESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef void d2(self, double t, complex[::1] vec, complex[:, ::1] out): + cpdef void d2(self, double t, complex[::1] vec, complex[:, ::1] out): cdef int i cdef QobjEvo c_op cdef complex expect @@ -2035,7 +2040,7 @@ cdef class PcSSESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef void collapse(self, double t, int which, double expect, + cpdef void collapse(self, double t, int which, double expect, complex[::1] vec, complex[::1] out): cdef QobjEvo c_op c_op = self.c_ops[which] @@ -2067,12 +2072,12 @@ cdef class PcSMESolver(StochasticSolver): self.cdcl_ops.append(op[1]) self.clcdr_ops.append(op[2]) - cdef void _normalize_inplace(self, complex[::1] vec): + cpdef void _normalize_inplace(self, complex[::1] vec): _normalize_rho(vec) @cython.boundscheck(False) @cython.wraparound(False) - cdef void photocurrent(self, double t, double dt, double[:] noise, + cpdef void photocurrent(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): cdef QobjEvo c_op cdef double rand @@ -2101,7 +2106,7 @@ cdef class PcSMESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) - cdef void photocurrent_pc(self, double t, double dt, double[:] noise, + cpdef void photocurrent_pc(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): cdef QobjEvo c_op cdef int i, which, num_coll=0, did_collapse @@ -2170,7 +2175,7 @@ cdef class PcSMESolver(StochasticSolver): return e @cython.boundscheck(False) - cdef void d1(self, double t, complex[::1] rho, complex[::1] out): + cpdef void d1(self, double t, complex[::1] rho, complex[::1] out): cdef int i cdef QobjEvo c_op cdef complex[::1] crho = self.func_buffer_1d[0,:] @@ -2187,7 +2192,7 @@ cdef class PcSMESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef void d2(self, double t, complex[::1] rho, complex[:, ::1] out): + cpdef void d2(self, double t, complex[::1] rho, complex[:, ::1] out): cdef int i cdef QobjEvo c_op cdef complex expect @@ -2204,7 +2209,7 @@ cdef class PcSMESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef void collapse(self, double t, int which, double expect, + cpdef void collapse(self, double t, int which, double expect, complex[::1] vec, complex[::1] out): cdef QobjEvo c_op c_op = self.clcdr_ops[which] @@ -2239,12 +2244,12 @@ cdef class PmSMESolver(StochasticSolver): self.postops2 = [op for op in sso.postops2] self.N_root = np.sqrt(self.l_vec) - cdef void _normalize_inplace(self, complex[::1] vec): + cpdef void _normalize_inplace(self, complex[::1] vec): _normalize_rho(vec) @cython.boundscheck(False) @cython.wraparound(False) - cdef void rouchon(self, double t, double dt, double[:] noise, + cpdef void rouchon(self, double t, double dt, double[:] noise, complex[::1] vec, complex[::1] out): cdef complex[::1] dy = self.expect_buffer_1d[0,:] cdef complex[::1] temp = self.buffer_1d[0,:] @@ -2325,7 +2330,7 @@ cdef class GenericSSolver(StochasticSolver): self.d2_func = sso.d2 - cdef void d1(self, double t, complex[::1] rho, complex[::1] out): + cpdef void d1(self, double t, complex[::1] rho, complex[::1] out): cdef np.ndarray[complex, ndim=1] in_np cdef np.ndarray[complex, ndim=1] out_np in_np = np.zeros((self.l_vec, ), dtype=complex) @@ -2334,7 +2339,7 @@ cdef class GenericSSolver(StochasticSolver): _axpy(self.dt, out_np, out) # d1 is += and * dt @cython.boundscheck(False) - cdef void d2(self, double t, complex[::1] rho, complex[:, ::1] out): + cpdef void d2(self, double t, complex[::1] rho, complex[:, ::1] out): cdef np.ndarray[complex, ndim=1] in_np cdef np.ndarray[complex, ndim=2] out_np cdef int i diff --git a/qutip/solve/stochastic.py b/qutip/solve/stochastic.py index dde9876911..36502a9296 100644 --- a/qutip/solve/stochastic.py +++ b/qutip/solve/stochastic.py @@ -123,7 +123,7 @@ def stochastic_solvers(): All solvers, except taylor2.0, are usable in both smesolve and (ssesolve) and for both heterodyne and homodyne. taylor2.0 only works for 1 - stochastic operator independent of time with the homodyne method. + stochastic opera__init__tor independent of time with the homodyne method. :func:`~general_stochastic` only accepts the derivative-free solvers: ``'euler'``, ``'platen'`` and ``'explicit1.5'``. @@ -206,7 +206,7 @@ class StochasticSolverOptions: sc_ops : list of time_dependent_object List of stochastic collapse operators. Each stochastic collapse operator will give a deterministic and stochastic contribution - to the equation of motion according to how the d1 and d2 functions + to the equation of __init__motion according to how the d1 and d2 functions are defined. Each element of the list is a separate operator, like ``c_ops``. @@ -441,16 +441,16 @@ def set_solver(self): self.solver_code = 100 self.solver = 'platen' elif self.solver in ['pred-corr', 'predictor-corrector', - 'pc-euler', 101]: + 'pc-euler', "pred_corr", 101]: self.solver_code = 101 self.solver = 'pred-corr' elif self.solver in ['milstein', 102, 1.0]: self.solver_code = 102 self.solver = 'milstein' - elif self.solver in ['milstein-imp', 103]: + elif self.solver in ['milstein-imp', 'milstein_imp', 103]: self.solver_code = 103 self.solver = 'milstein-imp' - elif self.solver in ['pred-corr-2', 'pc-euler-2', 'pc-euler-imp', 104]: + elif self.solver in ['pred-corr-2', 'pc-euler-2', 'pc-euler-imp', "pred_corr_a", 104]: self.solver_code = 104 self.solver = 'pred-corr-2' elif self.solver in ['Rouchon', 'rouchon', 120]: @@ -464,7 +464,7 @@ def set_solver(self): elif self.solver in ['taylor15', 'taylor1.5', None, 1.5, 152]: self.solver_code = 152 self.solver = 'taylor1.5' - elif self.solver in ['taylor15-imp', 'taylor1.5-imp', 153]: + elif self.solver in ['taylor15-imp', 'taylor1.5-imp', "taylor15_imp", 153]: self.solver_code = 153 self.solver = 'taylor1.5-imp' elif self.solver in ['taylor2.0', 'taylor20', 2.0, 202]: @@ -511,7 +511,7 @@ def set_solver(self): def smesolve(H, rho0, times, c_ops=[], sc_ops=[], e_ops=[], - _safe_mode=True, args={}, **kwargs): + _safe_mode=True, args={}, _dry_run=False, **kwargs): """ Solve stochastic master equation. Dispatch to specific solvers depending on the value of the `solver` keyword argument. @@ -629,6 +629,11 @@ def smesolve(H, rho0, times, c_ops=[], sc_ops=[], e_ops=[], sso.solver_obj = SMESolver sso.solver_name = "smesolve_" + sso.solver + if _dry_run: + ssolver = sso.solver_obj() + ssolver.set_solver(sso) + return ssolver + res = _sesolve_generic(sso, sso.options, sso.progress_bar) if e_ops_dict: @@ -638,7 +643,7 @@ def smesolve(H, rho0, times, c_ops=[], sc_ops=[], e_ops=[], def ssesolve(H, psi0, times, sc_ops=[], e_ops=[], - _safe_mode=True, args={}, **kwargs): + _safe_mode=True, args={}, _dry_run=False, **kwargs): """ Solve stochastic schrodinger equation. Dispatch to specific solvers depending on the value of the `solver` keyword argument. @@ -748,6 +753,11 @@ def ssesolve(H, psi0, times, sc_ops=[], e_ops=[], sso.solver_obj = SSESolver sso.solver_name = "ssesolve_" + sso.solver + if _dry_run: + ssolver = sso.solver_obj() + ssolver.set_solver(sso) + return ssolver + res = _sesolve_generic(sso, sso.options, sso.progress_bar) if e_ops_dict: diff --git a/qutip/solver/sode/check.py b/qutip/solver/sode/check.py new file mode 100644 index 0000000000..b7dfa70b78 --- /dev/null +++ b/qutip/solver/sode/check.py @@ -0,0 +1,11 @@ +import numpy + +def make_1_step(H, psi, sc_ops, solver, dt, dw=False): + ssolver = qt.smesolve(H, psi, [0,dt], sc_ops=c_ops, _dry_run=1, solver=solver, nsubsteps=1) + state = qt.operator_to_vector(ssolver.sso.state0).full().flatten() + func = getattr(ssolver, solver) + out = np.zeros_like(state) + if dw is False: + dw = np.array([1.] * 2 * len(sc_ops)) * dt**0.5 + func(0, dt, dw, state, out) + return qt.Qobj( qt.unstack_columns(out)) diff --git a/qutip/solver/sode/ito.py b/qutip/solver/sode/ito.py new file mode 100644 index 0000000000..7040fe6c2d --- /dev/null +++ b/qutip/solver/sode/ito.py @@ -0,0 +1,34 @@ +import numpy as np + +class ItoNoise: + def __init__(self, T, dt): + N = int(np.round(T / dt)) + self.T = T + self.dt = dt + self.noise = np.random.randn(N) * dt**0.5 + + def dw(self, dt): + # I(j) + N = int(np.round(dt /self.dt)) + return self.noise.reshape(-1, N).sum(axis=1) + + def dz(self, dt): + # I(0, j) + N = int(np.round(dt /self.dt)) + return self.noise.reshape(-1, N) @ np.arange(N-0.5, 0, -1) * self.dt + +class MultiNoise: + def __init__(self, T, dt, num=1): + N = int(np.round(T / dt)) + self.T = T + self.dt = dt + self.num = num + self.noise = np.random.randn(N, num) * dt**0.5 + + def dw(self, dt): + N = int(np.round(dt /self.dt)) + return self.noise.reshape(-1, N, self.num).sum(axis=1) + + def dz(self, dt): + N = int(np.round(dt /self.dt)) + return np.einsum("ijk,j", self.noise.reshape(-1, N, self.num), np.arange(N-0.5, 0, -1)) * self.dt diff --git a/qutip/solver/sode/ssystem.pxd b/qutip/solver/sode/ssystem.pxd index 8c7c7e47c5..6b82236e34 100644 --- a/qutip/solver/sode/ssystem.pxd +++ b/qutip/solver/sode/ssystem.pxd @@ -1,3 +1,4 @@ +#cython: language_level=3 from qutip.core.data cimport Data cdef class _StochasticSystem: From c2027761b0271f55608d0b790ab965db33870914 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 26 Jan 2023 18:46:46 -0500 Subject: [PATCH 029/602] all explicit methods added --- qutip/solver/sode/_sode.pyx | 98 ++++++++++++++- qutip/solver/sode/check.py | 49 +++++++- qutip/solver/sode/ito.py | 16 ++- qutip/solver/sode/ssystem.pxd | 12 +- qutip/solver/sode/ssystem.pyx | 220 +++++++++++++++++++++++++++++++++- 5 files changed, 385 insertions(+), 10 deletions(-) diff --git a/qutip/solver/sode/_sode.pyx b/qutip/solver/sode/_sode.pyx index a399f107c6..dbe0d2ea88 100644 --- a/qutip/solver/sode/_sode.pyx +++ b/qutip/solver/sode/_sode.pyx @@ -109,7 +109,7 @@ cpdef Data explicit15(_StochasticSystem system, t, Data state, double dt, double for i in range(num_ops): out = _data.add(out, d2[i], dw[i]) - V = _data.add(state, d1, 1./num_ops) + V = _data.add(state, d1, dt/num_ops) v2p = [] v2m = [] @@ -199,3 +199,99 @@ cpdef Data explicit15(_StochasticSystem system, t, Data state, double dt, double out = _data.add(out, d2m[j], ddw) return out + + + +cpdef Data milstein(_StochasticSystem system, t, Data state, double dt, double[:, :] dW): + """ + Chapter 10.3 Eq. (3.1) + Numerical Solution of Stochastic Differential Equations + By Peter E. Kloeden, Eckhard Platen + + dV = -iH*V*dt + d1*dt + d2_i*dW_i + + 0.5*d2_i' d2_j*(dW_i*dw_j -dt*delta_ij) + """ + cdef int i, j, num_ops = system.num_collapse + cdef double dw + + system.set_state(t, state) + + out = _data.add_dense(state, system.a(), dt) + + for i in range(num_ops): + _data.iadd_dense(out, system.bi(i), dW[i, 0]) + + for i in range(num_ops): + for j in range(i, num_ops): + dw = (dW[i, 0] * dW[j, 0] - dt * (i == j)) * (0.5 + (i != j) * 0.5) + _data.iadd_dense(out, system.Libj(i, j), dw) + + return out + + +cpdef Data pred_corr(_StochasticSystem system, t, Data state, double dt, double[:, :] dW, alpha=0.): + """ + Chapter 10.3 Eq. (3.1) + Numerical Solution of Stochastic Differential Equations + By Peter E. Kloeden, Eckhard Platen + + dV = -iH*V*dt + d1*dt + d2_i*dW_i + + 0.5*d2_i' d2_j*(dW_i*dw_j -dt*delta_ij) + """ + cdef int i, j, k, num_ops = system.num_collapse + cdef double eta=0.5 + + system.set_state(t, state) + + out = _data.add_dense(state, system.a(), dt*(1-alpha)) + euler = _data.add_dense(state, system.a(), dt) + + for i in range(num_ops): + _data.iadd_dense(euler, system.bi(i), dW[i, 0]) + _data.iadd_dense(out, system.bi(i), dW[i, 0]*eta) + _data.iadd_dense(out, system.Libj(i, i), dt*(alpha-1)*0.5) + + system.set_state(t+dt, euler) + if alpha: + _data.iadd_dense(out, system.a(), dt*alpha) + + for i in range(num_ops): + _data.iadd_dense(out, system.bi(i), dW[i, 0]*(1-eta)) + _data.iadd_dense(out, system.Libj(i, i), -dt*alpha*0.5) + + return out + + +cpdef Data taylor15(_StochasticSystem system, t, Data state, double dt, double[:, :] dW): + """ + Chapter 12.2 Eq. (2.18), + Numerical Solution of Stochastic Differential Equations + By Peter E. Kloeden, Eckhard Platen + """ + system.set_state(t, state) + cdef int i, j, k, num_ops = system.num_collapse + cdef double[:] dz, dw + + num_ops = system.num_collapse + dw = dW[:, 0] + dz = 0.5 *(dW[:, 0] + 1./np.sqrt(3) * dW[:, 1]) * dt + + out = _data.add_dense(state, system.a(), dt) + _data.iadd_dense(out, system.L0a(), 0.5 * dt*dt) + + for i in range(num_ops): + _data.iadd_dense(out, system.bi(i), dw[i]) + _data.iadd_dense(out, system.Libj(i, i), 0.5*(dw[i]*dw[i]-dt)) + _data.iadd_dense(out, system.Lia(i), dz[i]) + _data.iadd_dense(out, system.L0bi(i), dw[i] * dt - dz[i]) + _data.iadd_dense(out, system.LiLjbk(i, i, i), + 0.5 * ((1/3.) * dw[i] * dw[i] - dt) * dw[i]) + + for j in range(i+1, num_ops): + _data.iadd_dense(out, system.Libj(i, j), dw[i]*dw[j]) + _data.iadd_dense(out, system.LiLjbk(i, j, j), 0.5*(dw[j]*dw[j]-dt)*dw[i]) + _data.iadd_dense(out, system.LiLjbk(i, i, j), 0.5*(dw[i]*dw[i]-dt)*dw[j]) + for k in range(j+1, num_ops): + _data.iadd_dense(out, system.LiLjbk(i, j, k), dw[i]*dw[j]*dw[k]) + + return out diff --git a/qutip/solver/sode/check.py b/qutip/solver/sode/check.py index b7dfa70b78..606dca6363 100644 --- a/qutip/solver/sode/check.py +++ b/qutip/solver/sode/check.py @@ -1,4 +1,4 @@ -import numpy +import numpy as np def make_1_step(H, psi, sc_ops, solver, dt, dw=False): ssolver = qt.smesolve(H, psi, [0,dt], sc_ops=c_ops, _dry_run=1, solver=solver, nsubsteps=1) @@ -9,3 +9,50 @@ def make_1_step(H, psi, sc_ops, solver, dt, dw=False): dw = np.array([1.] * 2 * len(sc_ops)) * dt**0.5 func(0, dt, dw, state, out) return qt.Qobj( qt.unstack_columns(out)) + + +def L0(system, f): + def _func(t, rho, dt=1e-6): + n = rho.shape[0] + f0 = f(t, rho.data) + + out = (f(t+dt, rho) - f0) / dt + + jac = np.zeros((n, n), dtype=complex) + for i in range(n): + dxi = qt.basis(n, i).data + jac[:, i] = (f(t, rho + dt * dxi) - f0).to_array().flatten() / dt + out = out + qt.data.Dense(jac) @ system.drift(t, rho) + + for i, j in product(range(n), repeat=2): + dxi = qt.basis(n, i).data + dxj = qt.basis(n, j).data + sec = f(t, (rho + dxi * dt + dxj * dt)) + sec = sec - f(t, (rho + dxj * dt)) + sec = sec - f(t, (rho + dxi * dt)) + sec = sec + f0 + sec = sec / dt / dt * 0.5 + for k in range(system.num_collapse): + out = out + ( + sec + * qt.data.inner(dxi, system.diffusion(t, rho)[k]) + * qt.data.inner(dxj, system.diffusion(t, rho)[k]) + ) + return out + return _func + + +def Li(system, ii, f): + def _func(t, rho, dt=1e-6): + n = rho.shape[0] + jac = np.zeros((n, n), dtype=complex) + f0 = f(t, rho) + for i in range(n): + dxi = qt.basis(n, i).data + jac[:, i] = (f(t, (rho + dt * dxi)) - f0).to_array().flatten() + return qt.data.Dense(jac) @ system.diffusion(t, rho)[ii] / dt + return _func + + +def _check_equivalence(f, target, shape): + ... diff --git a/qutip/solver/sode/ito.py b/qutip/solver/sode/ito.py index 7040fe6c2d..294e517a36 100644 --- a/qutip/solver/sode/ito.py +++ b/qutip/solver/sode/ito.py @@ -31,4 +31,18 @@ def dw(self, dt): def dz(self, dt): N = int(np.round(dt /self.dt)) - return np.einsum("ijk,j", self.noise.reshape(-1, N, self.num), np.arange(N-0.5, 0, -1)) * self.dt + return np.einsum( + "ijk,j", + self.noise.reshape(-1, N, self.num), + np.arange(N-0.5, 0, -1) + ) * self.dt + + def dW(self, dt): + N = int(np.round(dt / self.dt)) + noise = self.noise + if noise.shape[0] % N: + noise = noise[:-noise.shape[0] % N] + out = np.empty((self.num, 2), dtype=float) + out[:, 0] = noise[:N, :].sum(axis=0) + out[:, 1] = np.arange(N-0.5, 0, -1) @ noise[:N, :] * self.dt + return out.T diff --git a/qutip/solver/sode/ssystem.pxd b/qutip/solver/sode/ssystem.pxd index 6b82236e34..947a31270c 100644 --- a/qutip/solver/sode/ssystem.pxd +++ b/qutip/solver/sode/ssystem.pxd @@ -1,5 +1,5 @@ #cython: language_level=3 -from qutip.core.data cimport Data +from qutip.core.data cimport Data, Dense cdef class _StochasticSystem: cdef readonly int num_collapse @@ -8,9 +8,17 @@ cdef class _StochasticSystem: cdef Data state cdef double t cdef object imp + cdef int is_set cpdef Data drift(self, t, Data state) cpdef list diffusion(self, t, Data state) - cdef void set_state(self, double t, Data state) + cpdef void set_state(self, double t, Data state) + cpdef Data a(self) + cpdef Data bi(self, int i) + cpdef Data Libj(self, int i, int j) + cpdef Data Lia(self, int i) + cpdef Data L0bi(self, int i) + cpdef Data LiLjbk(self, int i, int j, int k) + cpdef Data L0a(self) diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index 777fc1290e..ff3dab67ef 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -5,7 +5,7 @@ Class to represent a stochastic differential equation system. from qutip.core import data as _data from qutip.core.cy.qobjevo cimport QobjEvo -from qutip.core.data cimport Data +from qutip.core.data cimport Data, dense, Dense cimport cython import numpy as np from qutip.core import spre, spost, liouvillian @@ -14,23 +14,71 @@ __all__ = [ "GeneralStochasticSystem", "StochasticOpenSystem", "StochasticClosedSystem" ] +@cython.boundscheck(False) +@cython.initializedcheck(False) +cdef Dense _dense_wrap(double complex [::1] x): + return dense.wrap(&x[0], x.shape[0], 1) -cdef class _StochasticSystem: +cdef class _StochasticSystem: def __init__(self, a, b): self.d1 = a self.d2 = b self.num_collapse = 1 + self.is_set = False cpdef Data drift(self, t, Data state): - return self.d1(t, state) + raise NotImplementedError cpdef list diffusion(self, t, Data state): - return self.d2(t, state) + raise NotImplementedError - cdef void set_state(self, double t, Data state): + cpdef void set_state(self, double t, Data state): self.t = t self.state = state + self.is_set = True + + cpdef Data a(self): + """ + Drift term + """ + raise NotImplementedError + + cpdef Data bi(self, int i): + """ + Diffusion term for the ``i``th operator. + """ + raise NotImplementedError + + cpdef Data Libj(self, int i, int j): + """ + bi_n * d bj / dx_n + """ + raise NotImplementedError + + cpdef Data Lia(self, int i): + """ + bi_n * d a / dx_n + """ + raise NotImplementedError + + cpdef Data L0bi(self, int i): + """ + d/dt + a_n * d bi / dx_n + sum_k bk_n bk_m *0.5 d**2 (bi) / (dx_n dx_m) + """ + raise NotImplementedError + + cpdef Data LiLjbk(self, int i, int j, int k): + """ + bi_n * d/dx_n ( bj_m * d bk / dx_m) + """ + raise NotImplementedError + + cpdef Data L0a(self): + """ + d/dt + a_n * d a / dx_n + sum_k bk_n bk_m *0.5 d**2 (a) / (dx_n dx_m) + """ + raise NotImplementedError cdef class GeneralStochasticSystem(_StochasticSystem): @@ -103,6 +151,14 @@ cdef class StochasticClosedSystem(_StochasticSystem): cdef class StochasticOpenSystem(_StochasticSystem): cdef QobjEvo L cdef list c_ops + cdef int state_size + cdef double dt + + cdef readonly Dense _a, temp, _L0a + cdef readonly complex[::1] expect_Cv + cdef readonly complex[:, ::1] expect_Cb, _b, _La, _L0b + cdef readonly complex[:, :, ::1] _Lb + cdef readonly complex[:, :, :, ::1] _LLb def __init__(self, H, c_ops, heterodyne): self.L = H + liouvillian(None, c_ops) @@ -118,6 +174,8 @@ cdef class StochasticOpenSystem(_StochasticSystem): self.num_collapse = len(self.c_ops) self.issuper = True self.dims = self.L.dims + self.state_size = self.L.shape[1] + self.is_set = 0 cpdef Data drift(self, t, Data state): return self.L.matmul_data(t, state) @@ -133,3 +191,155 @@ cdef class StochasticOpenSystem(_StochasticSystem): expect = _data.trace_oper_ket(vec) out.append(_data.add(vec, state, -expect)) return out + + cpdef void set_state(self, double t, Data state): + cdef n, l + self.t = t + self.state = _data.to(_data.Dense, state) + + if not self.is_set: + n = self.num_collapse + l = self.state_size + self.is_set = 1 + self._a = dense.zeros(self.state_size, 1) + self.temp = dense.zeros(self.state_size, 1) + self._L0a = dense.zeros(self.state_size, 1) + self.expect_Cv = np.zeros(n, dtype=complex) + self.expect_Cb = np.zeros((n, n), dtype=complex) + self._b = np.zeros((n, l), dtype=complex) + self._L0b = np.zeros((n, l), dtype=complex) + self._Lb = np.zeros((n, n, l), dtype=complex) + self._LLb = np.zeros((n, n, n, l), dtype=complex) + self._La = np.zeros((n, l), dtype=complex) + self.dt = 1e-6 # Make an options + + cpdef Data a(self): + + self.is_set = 2 + _data.imul_dense(self._a, 0) + self.L.matmul_data(self.t, self.state, self._a) + return self._a + + cpdef Data bi(self, int n): + cdef int i + cdef QobjEvo c_op + cdef Dense b_vec + + for i in range(self.num_collapse): + c_op = self.c_ops[i] + b_vec = _dense_wrap(self._b[i, :]) + _data.imul_dense(b_vec, 0) + c_op.matmul_data(self.t, self.state, b_vec) + self.expect_Cv[i] = _data.trace_oper_ket(b_vec) + _data.iadd_dense(b_vec, self.state, -self.expect_Cv[i]) + + return _dense_wrap(self._b[n, :]) + + cpdef Data Libj(self, int n, int m): + cdef int i, j + cdef QobjEvo c_op + cdef Dense b_vec, Lb_vec + cdef complex expect + + for i in range(self.num_collapse): + c_op = self.c_ops[i] + for j in range(i, self.num_collapse): + b_vec = _dense_wrap(self._b[j, :]) + Lb_vec = _dense_wrap(self._Lb[i, j, :]) + _data.imul_dense(Lb_vec, 0) + c_op.matmul_data(self.t, b_vec, Lb_vec) + self.expect_Cb[i,j] = _data.trace_oper_ket(Lb_vec) + _data.iadd_dense(Lb_vec, b_vec, -self.expect_Cv[i]) + _data.iadd_dense(Lb_vec, self.state, -self.expect_Cb[i,j]) + + if m >= n: + # We only support commutative diffusion + return _dense_wrap(self._Lb[n, m, :]) + return _dense_wrap(self._Lb[m, n, :]) + + cpdef Data Lia(self, int n): + cdef int i + cdef QobjEvo c_op + cdef Dense b_vec, La_vec + + for i in range(self.num_collapse): + b_vec = _dense_wrap(self._b[i, :]) + La_vec = _dense_wrap(self._La[i, :]) + _data.imul_dense(La_vec, 0.) + self.L.matmul_data(self.t, b_vec, La_vec) + + return _dense_wrap(self._La[n, :]) + + cpdef Data L0bi(self, int n): + # L0bi = abi' + dbi/dt + Sum_j bjbjbi"/2 + cdef int i, j + cdef QobjEvo c_op + cdef Dense b_vec, L0b_vec, a + + for i in range(self.num_collapse): + c_op = self.c_ops[i] + L0b_vec = _dense_wrap(self._L0b[i, :]) + b_vec = _dense_wrap(self._b[i, :]) + _data.imul_dense(L0b_vec, 0.) + + # db/dt + c_op.matmul_data(self.t + self.dt, self.state, L0b_vec) + expect = _data.trace_oper_ket(L0b_vec) + _data.iadd_dense(L0b_vec, self.state, -expect) + _data.iadd_dense(L0b_vec, b_vec, -1) + _data.imul_dense(L0b_vec, 1/self.dt) + + # ab' + _data.imul_dense(self.temp, 0) + c_op.matmul_data(self.t, self._a, self.temp) + expect = _data.trace_oper_ket(self.temp) + _data.iadd_dense(L0b_vec, self.temp, 1) + _data.iadd_dense(L0b_vec, self._a, -self.expect_Cv[i]) + _data.iadd_dense(L0b_vec, self.state, -expect) + + # bbb" : expect_Cb[i,j] only defined for j>=i + for j in range(i): + b_vec = _dense_wrap(self._b[j, :]) + _data.iadd_dense(L0b_vec, b_vec, -self.expect_Cb[j,i]) + for j in range(i, self.num_collapse): + b_vec = _dense_wrap(self._b[j, :]) + _data.iadd_dense(L0b_vec, b_vec, -self.expect_Cb[i,j]) + + return _dense_wrap(self._L0b[n, :]) + + cpdef Data LiLjbk(self, int n, int m, int o): + # LiLjbk = bi(bj'bk'+bjbk"), i<=j<=k + # sc_ops must commute (LiLjbk = LjLibk = LkLjbi) + cdef int i, j, k + cdef QobjEvo c_op + cdef Dense bj_vec, bk_vec, LLb_vec, Lb_vec + + for i in range(self.num_collapse): + for j in range(i,self.num_collapse): + for k in range(j,self.num_collapse): + + c_op = self.c_ops[i] + LLb_vec = _dense_wrap(self._LLb[i, j, k, :]) + Lb_vec = _dense_wrap(self._Lb[j, k, :]) + bj_vec = _dense_wrap(self._b[j, :]) + bk_vec = _dense_wrap(self._b[k, :]) + _data.imul_dense(LLb_vec, 0.) + + c_op.matmul_data(self.t, Lb_vec, LLb_vec) + expect = _data.trace_oper_ket(LLb_vec) + + _data.iadd_dense(LLb_vec, Lb_vec, -self.expect_Cv[i]) + _data.iadd_dense(LLb_vec, self.state, -expect) + _data.iadd_dense(LLb_vec, bj_vec, -self.expect_Cb[i,k]) + _data.iadd_dense(LLb_vec, bk_vec, -self.expect_Cb[i,j]) + + return _dense_wrap(self._LLb[n, m, o, :]) + + cpdef Data L0a(self): + # L0a = a'a + da/dt + bba"/2 (a" = 0) + _data.imul_dense(self._L0a, 0.) + self.L.matmul_data(self.t + self.dt, self.state, self._L0a) + _data.iadd_dense(self._L0a, self._a, -1) + _data.imul_dense(self._L0a, 1/self.dt) + self.L.matmul_data(self.t, self._a, self._L0a) + return self._L0a From 88c48a92d8060119a526aa0abee4420c0f5bb140 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 27 Jan 2023 11:32:37 -0500 Subject: [PATCH 030/602] Safe StochasticOpenSystem derrivatives --- qutip/solver/sode/sode.py | 45 ++++++++++++++ qutip/solver/sode/ssystem.pyx | 107 ++++++++++++++++++++++++++-------- 2 files changed, 128 insertions(+), 24 deletions(-) diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 4cbe348ff8..b64823f7e6 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -202,7 +202,52 @@ class Explicit1_5_SODE(_Explicit_Simple_Integrator): N_dw = 2 +class Taylor1_5_SODE(_Explicit_Simple_Integrator): + """ + Order 1.5 strong Taylor scheme. Solver with more terms of the + Ito-Taylor expansion. Default solver for :obj:`~smesolve` and + :obj:`~ssesolve`. See eq. (4.6) of chapter 10.4 of [1]_. + + - Order strong 1.5 + """ + stepper = _sode.taylor15 + N_dw = 2 + + +class Milstein_SODE(_Explicit_Simple_Integrator): + """ + An order 1.0 strong Taylor scheme. Better approximate numerical + solution to stochastic differential equations. See eq. (2.9) of + chapter 12.2 of [1]_. + + - Order strong 1.0 + """ + stepper = _sode.milstein + N_dw = 1 + + +class PredCorr_SODE(_Explicit_Simple_Integrator): + """ + Generalization of the trapezoidal method to stochastic differential + equations. More stable than explicit methods. See eq. (5.4) of + chapter 15.5 of [1]_. + + - Order strong 0.5, weak 1.0 + - Codes to only correct the stochastic part (:math:`\\alpha=0`, + :math:`\\eta=1/2`): ``'pred-corr'``, ``'predictor-corrector'`` or + ``'pc-euler'`` + - Codes to correct both the stochastic and deterministic parts + (:math:`\\alpha=1/2`, :math:`\\eta=1/2`): ``'pc-euler-imp'``, + ``'pc-euler-2'`` or ``'pred-corr-2'`` + """ + stepper = _sode.pred_corr + N_dw = 1 + + StochasticSolver.add_integrator(EulerSODE, "euler") StochasticSolver.add_integrator(EulerSODE, "euler-maruyama") StochasticSolver.add_integrator(PlatenSODE, "platen") StochasticSolver.add_integrator(Explicit1_5_SODE, "explicit1.5") +StochasticSolver.add_integrator(Taylor1_5_SODE, "taylor15") +StochasticSolver.add_integrator(PredCorr_SODE, "pred_corr") +StochasticSolver.add_integrator(Milstein_SODE, "milstein") diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index ff3dab67ef..fdd84c9b03 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -153,6 +153,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): cdef list c_ops cdef int state_size cdef double dt + cdef bint _a_set, _b_set, _Lb_set, _L0b_set, _La_set, _LLb_set, _L0a_set cdef readonly Dense _a, temp, _L0a cdef readonly complex[::1] expect_Cv @@ -196,6 +197,13 @@ cdef class StochasticOpenSystem(_StochasticSystem): cdef n, l self.t = t self.state = _data.to(_data.Dense, state) + self._a_set = False + self._b_set = False + self._Lb_set = False + self._L0b_set = False + self._La_set = False + self._LLb_set = False + self._L0a_set = False if not self.is_set: n = self.num_collapse @@ -214,17 +222,28 @@ cdef class StochasticOpenSystem(_StochasticSystem): self.dt = 1e-6 # Make an options cpdef Data a(self): + if not self._a_set: + self._compute_a() + return self._a - self.is_set = 2 + cdef void _compute_a(self) except *: + if not self._is_set: + raise RuntimeError _data.imul_dense(self._a, 0) self.L.matmul_data(self.t, self.state, self._a) - return self._a + self._a_set = True - cpdef Data bi(self, int n): + cpdef Data bi(self, int i): + if not self._b_set: + self._compute_b() + return _dense_wrap(self._b[i, :]) + + cdef void _compute_b(self) except *: + if not self._is_set: + raise RuntimeError cdef int i cdef QobjEvo c_op cdef Dense b_vec - for i in range(self.num_collapse): c_op = self.c_ops[i] b_vec = _dense_wrap(self._b[i, :]) @@ -232,14 +251,23 @@ cdef class StochasticOpenSystem(_StochasticSystem): c_op.matmul_data(self.t, self.state, b_vec) self.expect_Cv[i] = _data.trace_oper_ket(b_vec) _data.iadd_dense(b_vec, self.state, -self.expect_Cv[i]) + self._b_set = True - return _dense_wrap(self._b[n, :]) - - cpdef Data Libj(self, int n, int m): + cpdef Data Libj(self, int i, int j): + if not self._Lb_set: + self._compute_Lb() + # We only support commutative diffusion + if i > j: + j, i = i, j + return _dense_wrap(self._Lb[i, j, :]) + + cdef void _compute_Lb(self) except *: cdef int i, j cdef QobjEvo c_op cdef Dense b_vec, Lb_vec cdef complex expect + if not self._b_set: + self._compute_b() for i in range(self.num_collapse): c_op = self.c_ops[i] @@ -251,30 +279,41 @@ cdef class StochasticOpenSystem(_StochasticSystem): self.expect_Cb[i,j] = _data.trace_oper_ket(Lb_vec) _data.iadd_dense(Lb_vec, b_vec, -self.expect_Cv[i]) _data.iadd_dense(Lb_vec, self.state, -self.expect_Cb[i,j]) + self._Lb_set = True - if m >= n: - # We only support commutative diffusion - return _dense_wrap(self._Lb[n, m, :]) - return _dense_wrap(self._Lb[m, n, :]) + cpdef Data Lia(self, int i): + if not self._La_set: + self._compute_La() + return _dense_wrap(self._La[i, :]) - cpdef Data Lia(self, int n): + cdef void _compute_La(self) except *: cdef int i cdef QobjEvo c_op cdef Dense b_vec, La_vec + if not self._b_set: + self._compute_b() for i in range(self.num_collapse): b_vec = _dense_wrap(self._b[i, :]) La_vec = _dense_wrap(self._La[i, :]) _data.imul_dense(La_vec, 0.) self.L.matmul_data(self.t, b_vec, La_vec) + self._La_set = True - return _dense_wrap(self._La[n, :]) - - cpdef Data L0bi(self, int n): + cpdef Data L0bi(self, int i): # L0bi = abi' + dbi/dt + Sum_j bjbjbi"/2 + if not self._L0b_set: + self._compute_L0b() + return _dense_wrap(self._L0b[i, :]) + + cdef void _compute_L0b(self) except *: cdef int i, j cdef QobjEvo c_op - cdef Dense b_vec, L0b_vec, a + cdef Dense b_vec, L0b_vec + if not self._b_set: + self._compute_b() + if not self._a_set: + self._compute_a() for i in range(self.num_collapse): c_op = self.c_ops[i] @@ -304,20 +343,34 @@ cdef class StochasticOpenSystem(_StochasticSystem): for j in range(i, self.num_collapse): b_vec = _dense_wrap(self._b[j, :]) _data.iadd_dense(L0b_vec, b_vec, -self.expect_Cb[i,j]) + self._L0b_set = True - return _dense_wrap(self._L0b[n, :]) - - cpdef Data LiLjbk(self, int n, int m, int o): + cpdef Data LiLjbk(self, int i, int j, int k): + # LiLjbk = bi(bj'bk'+bjbk"), i<=j<=k + if not self._L0b_set: + self._compute_LLb() + # Only commutative noise supported + # Definied for i <= j <= k + # Simple bubble sort to order the terms + if i>j: i, j = j, i + if j>k: + j, k = k, j + if i>j: i, j = j, i + + return _dense_wrap(self._LLb[i, j, k, :]) + + cdef void _compute_LLb(self) except *: # LiLjbk = bi(bj'bk'+bjbk"), i<=j<=k # sc_ops must commute (LiLjbk = LjLibk = LkLjbi) cdef int i, j, k cdef QobjEvo c_op cdef Dense bj_vec, bk_vec, LLb_vec, Lb_vec + if not self._Lb_set: + self._compute_Lb() for i in range(self.num_collapse): - for j in range(i,self.num_collapse): - for k in range(j,self.num_collapse): - + for j in range(i, self.num_collapse): + for k in range(j, self.num_collapse): c_op = self.c_ops[i] LLb_vec = _dense_wrap(self._LLb[i, j, k, :]) Lb_vec = _dense_wrap(self._Lb[j, k, :]) @@ -333,13 +386,19 @@ cdef class StochasticOpenSystem(_StochasticSystem): _data.iadd_dense(LLb_vec, bj_vec, -self.expect_Cb[i,k]) _data.iadd_dense(LLb_vec, bk_vec, -self.expect_Cb[i,j]) - return _dense_wrap(self._LLb[n, m, o, :]) + self._LLb_set = True cpdef Data L0a(self): + # L0a = a'a + da/dt + bba"/2 (a" = 0) + if not self._L0a_set: + self._compute_L0a() + return self._L0a + + cdef void _compute_L0a(self) except *: # L0a = a'a + da/dt + bba"/2 (a" = 0) _data.imul_dense(self._L0a, 0.) self.L.matmul_data(self.t + self.dt, self.state, self._L0a) _data.iadd_dense(self._L0a, self._a, -1) _data.imul_dense(self._L0a, 1/self.dt) self.L.matmul_data(self.t, self._a, self._L0a) - return self._L0a + self._L0a_set = True From bb7e845d6162f8b7707072244e3f1b5a0dd7ad8e Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 27 Jan 2023 17:04:45 -0500 Subject: [PATCH 031/602] add check code --- qutip/solver/sode/sode.py | 9 ++++++--- qutip/solver/sode/ssystem.pxd | 2 +- qutip/solver/sode/ssystem.pyx | 28 ++++++++++++++-------------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index b64823f7e6..5b90b312aa 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -206,7 +206,8 @@ class Taylor1_5_SODE(_Explicit_Simple_Integrator): """ Order 1.5 strong Taylor scheme. Solver with more terms of the Ito-Taylor expansion. Default solver for :obj:`~smesolve` and - :obj:`~ssesolve`. See eq. (4.6) of chapter 10.4 of [1]_. + :obj:`~ssesolve`. See eq. (4.6) of chapter 10.4 of Peter E. Kloeden and + Exkhard Platen, *Numerical Solution of Stochastic Differential Equations*. - Order strong 1.5 """ @@ -218,7 +219,8 @@ class Milstein_SODE(_Explicit_Simple_Integrator): """ An order 1.0 strong Taylor scheme. Better approximate numerical solution to stochastic differential equations. See eq. (2.9) of - chapter 12.2 of [1]_. + chapter 12.2 of Peter E. Kloeden and Exkhard Platen, + *Numerical Solution of Stochastic Differential Equations*.. - Order strong 1.0 """ @@ -230,7 +232,8 @@ class PredCorr_SODE(_Explicit_Simple_Integrator): """ Generalization of the trapezoidal method to stochastic differential equations. More stable than explicit methods. See eq. (5.4) of - chapter 15.5 of [1]_. + chapter 15.5 of Peter E. Kloeden and Exkhard Platen, + *Numerical Solution of Stochastic Differential Equations*. - Order strong 0.5, weak 1.0 - Codes to only correct the stochastic part (:math:`\\alpha=0`, diff --git a/qutip/solver/sode/ssystem.pxd b/qutip/solver/sode/ssystem.pxd index 947a31270c..f3c5a5e5ef 100644 --- a/qutip/solver/sode/ssystem.pxd +++ b/qutip/solver/sode/ssystem.pxd @@ -8,7 +8,7 @@ cdef class _StochasticSystem: cdef Data state cdef double t cdef object imp - cdef int is_set + cdef int _is_set cpdef Data drift(self, t, Data state) diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index fdd84c9b03..626d80090d 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -25,7 +25,7 @@ cdef class _StochasticSystem: self.d1 = a self.d2 = b self.num_collapse = 1 - self.is_set = False + self._is_set = False cpdef Data drift(self, t, Data state): raise NotImplementedError @@ -36,7 +36,7 @@ cdef class _StochasticSystem: cpdef void set_state(self, double t, Data state): self.t = t self.state = state - self.is_set = True + self._is_set = True cpdef Data a(self): """ @@ -176,7 +176,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): self.issuper = True self.dims = self.L.dims self.state_size = self.L.shape[1] - self.is_set = 0 + self._is_set = 0 cpdef Data drift(self, t, Data state): return self.L.matmul_data(t, state) @@ -189,7 +189,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): for i in range(self.num_collapse): c_op = self.c_ops[i] vec = c_op.matmul_data(t, state) - expect = _data.trace_oper_ket(vec) + expect = _data.trace_oper_ket_dense(vec) out.append(_data.add(vec, state, -expect)) return out @@ -205,10 +205,10 @@ cdef class StochasticOpenSystem(_StochasticSystem): self._LLb_set = False self._L0a_set = False - if not self.is_set: + if not self._is_set: n = self.num_collapse l = self.state_size - self.is_set = 1 + self._is_set = 1 self._a = dense.zeros(self.state_size, 1) self.temp = dense.zeros(self.state_size, 1) self._L0a = dense.zeros(self.state_size, 1) @@ -249,7 +249,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): b_vec = _dense_wrap(self._b[i, :]) _data.imul_dense(b_vec, 0) c_op.matmul_data(self.t, self.state, b_vec) - self.expect_Cv[i] = _data.trace_oper_ket(b_vec) + self.expect_Cv[i] = _data.trace_oper_ket_dense(b_vec) _data.iadd_dense(b_vec, self.state, -self.expect_Cv[i]) self._b_set = True @@ -276,7 +276,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): Lb_vec = _dense_wrap(self._Lb[i, j, :]) _data.imul_dense(Lb_vec, 0) c_op.matmul_data(self.t, b_vec, Lb_vec) - self.expect_Cb[i,j] = _data.trace_oper_ket(Lb_vec) + self.expect_Cb[i,j] = _data.trace_oper_ket_dense(Lb_vec) _data.iadd_dense(Lb_vec, b_vec, -self.expect_Cv[i]) _data.iadd_dense(Lb_vec, self.state, -self.expect_Cb[i,j]) self._Lb_set = True @@ -310,8 +310,8 @@ cdef class StochasticOpenSystem(_StochasticSystem): cdef int i, j cdef QobjEvo c_op cdef Dense b_vec, L0b_vec - if not self._b_set: - self._compute_b() + if not self._Lb_set: + self._compute_Lb() if not self._a_set: self._compute_a() @@ -323,7 +323,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): # db/dt c_op.matmul_data(self.t + self.dt, self.state, L0b_vec) - expect = _data.trace_oper_ket(L0b_vec) + expect = _data.trace_oper_ket_dense(L0b_vec) _data.iadd_dense(L0b_vec, self.state, -expect) _data.iadd_dense(L0b_vec, b_vec, -1) _data.imul_dense(L0b_vec, 1/self.dt) @@ -331,7 +331,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): # ab' _data.imul_dense(self.temp, 0) c_op.matmul_data(self.t, self._a, self.temp) - expect = _data.trace_oper_ket(self.temp) + expect = _data.trace_oper_ket_dense(self.temp) _data.iadd_dense(L0b_vec, self.temp, 1) _data.iadd_dense(L0b_vec, self._a, -self.expect_Cv[i]) _data.iadd_dense(L0b_vec, self.state, -expect) @@ -347,7 +347,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): cpdef Data LiLjbk(self, int i, int j, int k): # LiLjbk = bi(bj'bk'+bjbk"), i<=j<=k - if not self._L0b_set: + if not self._LLb_set: self._compute_LLb() # Only commutative noise supported # Definied for i <= j <= k @@ -379,7 +379,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): _data.imul_dense(LLb_vec, 0.) c_op.matmul_data(self.t, Lb_vec, LLb_vec) - expect = _data.trace_oper_ket(LLb_vec) + expect = _data.trace_oper_ket_dense(LLb_vec) _data.iadd_dense(LLb_vec, Lb_vec, -self.expect_Cv[i]) _data.iadd_dense(LLb_vec, self.state, -expect) From d4f5bc2deee860621ce08f6864b14fe0617b1d0e Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 27 Jan 2023 17:07:07 -0500 Subject: [PATCH 032/602] add check code --- qutip/solver/sode/check.py | 97 ++++++++++++++++++++++++++++++++------ 1 file changed, 82 insertions(+), 15 deletions(-) diff --git a/qutip/solver/sode/check.py b/qutip/solver/sode/check.py index 606dca6363..5fc53a874b 100644 --- a/qutip/solver/sode/check.py +++ b/qutip/solver/sode/check.py @@ -1,32 +1,35 @@ import numpy as np +from itertools import product +from qutip.core import data as _data +import qutip def make_1_step(H, psi, sc_ops, solver, dt, dw=False): - ssolver = qt.smesolve(H, psi, [0,dt], sc_ops=c_ops, _dry_run=1, solver=solver, nsubsteps=1) - state = qt.operator_to_vector(ssolver.sso.state0).full().flatten() + ssolver = qutip.smesolve(H, psi, [0,dt], sc_ops=c_ops, _dry_run=1, solver=solver, nsubsteps=1) + state = qutip.operator_to_vector(ssolver.sso.state0).full().flatten() func = getattr(ssolver, solver) out = np.zeros_like(state) if dw is False: dw = np.array([1.] * 2 * len(sc_ops)) * dt**0.5 func(0, dt, dw, state, out) - return qt.Qobj( qt.unstack_columns(out)) + return qutip.Qobj( qutip.unstack_columns(out)) def L0(system, f): def _func(t, rho, dt=1e-6): n = rho.shape[0] - f0 = f(t, rho.data) + f0 = f(t, rho) out = (f(t+dt, rho) - f0) / dt jac = np.zeros((n, n), dtype=complex) for i in range(n): - dxi = qt.basis(n, i).data + dxi = qutip.basis(n, i).data jac[:, i] = (f(t, rho + dt * dxi) - f0).to_array().flatten() / dt - out = out + qt.data.Dense(jac) @ system.drift(t, rho) + out = out + qutip.data.Dense(jac) @ system.drift(t, rho) for i, j in product(range(n), repeat=2): - dxi = qt.basis(n, i).data - dxj = qt.basis(n, j).data + dxi = qutip.basis(n, i).data + dxj = qutip.basis(n, j).data sec = f(t, (rho + dxi * dt + dxj * dt)) sec = sec - f(t, (rho + dxj * dt)) sec = sec - f(t, (rho + dxi * dt)) @@ -35,24 +38,88 @@ def _func(t, rho, dt=1e-6): for k in range(system.num_collapse): out = out + ( sec - * qt.data.inner(dxi, system.diffusion(t, rho)[k]) - * qt.data.inner(dxj, system.diffusion(t, rho)[k]) + * qutip.data.inner(dxi, system.diffusion(t, rho)[k]) + * qutip.data.inner(dxj, system.diffusion(t, rho)[k]) ) return out return _func -def Li(system, ii, f): +def L(system, ii, f): def _func(t, rho, dt=1e-6): n = rho.shape[0] jac = np.zeros((n, n), dtype=complex) f0 = f(t, rho) for i in range(n): - dxi = qt.basis(n, i).data + dxi = qutip.basis(n, i).data jac[:, i] = (f(t, (rho + dt * dxi)) - f0).to_array().flatten() - return qt.data.Dense(jac) @ system.diffusion(t, rho)[ii] / dt + return qutip.data.Dense(jac) @ system.diffusion(t, rho)[ii] / dt + return _func + + +def LL(system, ii, jj, f): + # Can be implemented as 2 calls of ``L``, but this use 2 ``dt`` which + # cannot be different. + def _func(t, rho, dt=1e-6): + f0 = f(t, rho) + bi = system.diffusion(t, rho)[ii] + bj = system.diffusion(t, rho)[jj] + out = rho *0. + n = rho.shape[0] + + for i, j in product(range(n), repeat=2): + dxi = qutip.basis(n, i, dtype="Dense").data + dxj = qutip.basis(n, j, dtype="Dense").data + sec = f(t, (rho + dxi * dt + dxj * dt)) + sec = sec - f(t, (rho + dxj * dt)) + sec = sec - f(t, (rho + dxi * dt)) + sec = sec + f0 + sec = sec / dt / dt + + out = out + ( + sec * qutip.data.inner(dxi, bi) * qutip.data.inner(dxj, bj) + ) + df = (f(t, (rho + dxj * dt)) - f0) / dt + db = (system.diffusion(t, (rho + dxi * dt))[jj] - system.diffusion(t, rho)[jj] ) / dt + + out = out + ( + df * qutip.data.inner(dxi, bi) * qutip.data.inner(dxj, db) + ) + + return out return _func -def _check_equivalence(f, target, shape): - ... +def _check_equivalence(f, target, args): + dts = np.logspace(-5, -1, 9) + errors_dt = [ + _data.norm.l2(f(*args, dt=dt) - target) / dt + for dt in dts + ] + poly = np.polyfit(dts, errors_dt, 1) + return -1 < poly[0] < 1 + + +def run_derr_check(solver, t, state): + N = solver.num_collapse + a = solver.drift + solver.set_state(t, state) + + assert solver.drift(t, state) == solver.a() + assert _check_equivalence(L0(solver, a), solver.L0a(), (t, state)) + + for i in range(N): + b = lambda *args: solver.diffusion(*args)[i] + assert b(t, state) == solver.bi(i) + assert _check_equivalence(L0(solver, b), solver.L0bi(i), (t, state)) + assert _check_equivalence(L(solver, i, a), solver.Lia(i), (t, state)) + + for j in range(N): + assert _check_equivalence( + L(solver, j, b), solver.Libj(j, i), (t, state) + ) + + for k in range(N): + assert _check_equivalence( + LL(solver, k, j, b), solver.LiLjbk(k, j, i), (t, state) + ) From 0a95382774db0b5466423cd0dc51dc0f1336326b Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 31 Jan 2023 11:59:02 -0500 Subject: [PATCH 033/602] More debug tools --- qutip/solver/mcsolve.py | 4 +- qutip/solver/sode/_sode.pyx | 661 +++++++++++++++++++++--------------- qutip/solver/sode/sode.py | 22 +- 3 files changed, 390 insertions(+), 297 deletions(-) diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index c0af05b5d6..cffe8d8668 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -225,12 +225,12 @@ def reset(self, hard=False): def _prob_func(self, state): if self.issuper: - return _data.norm.trace_oper_ket(state) + return _data.trace_oper_ket(state) return _data.norm.l2(state)**2 def _norm_func(self, state): if self.issuper: - return _data.norm.trace_oper_ket(state) + return _data.trace_oper_ket(state) return _data.norm.l2(state) def _find_collapse_time(self, norm_old, norm, t_prev, t_final): diff --git a/qutip/solver/sode/_sode.pyx b/qutip/solver/sode/_sode.pyx index dbe0d2ea88..470db90392 100644 --- a/qutip/solver/sode/_sode.pyx +++ b/qutip/solver/sode/_sode.pyx @@ -2,296 +2,393 @@ from qutip.core import data as _data from qutip.core.cy.qobjevo cimport QobjEvo -from qutip.core.data cimport Data +from qutip.core.data cimport Data, Dense from collections import defaultdict cimport cython from qutip.solver.sode.ssystem cimport _StochasticSystem import numpy as np -@cython.boundscheck(False) -@cython.wraparound(False) -cpdef Data euler(_StochasticSystem system, t, Data state, double dt, double[:, :] dW): - """ - Integration scheme: - Basic Euler order 0.5 - dV = d1 dt + d2_i dW_i - Numerical Solution of Stochastic Differential Equations - By Peter E. Kloeden, Eckhard Platen - """ - cdef int i - - a = system.drift(t, state) - b = system.diffusion(t, state) - new_state = _data.add(state, a, dt) - for i in range(system.num_collapse): - new_state = _data.add(new_state, b[i], dW[i, 0]) - return new_state - - -@cython.boundscheck(False) -@cython.wraparound(False) -cpdef Data platen(_StochasticSystem system, t, Data state, double dt, double[:, :] dW): - """ - Platen rhs function for both master eq and schrodinger eq. - dV = -iH* (V+Vt)/2 * dt + (d1(V)+d1(Vt))/2 * dt - + (2*d2_i(V)+d2_i(V+)+d2_i(V-))/4 * dW_i - + (d2_i(V+)-d2_i(V-))/4 * (dW_i**2 -dt) * dt**(-.5) - - Vt = V -iH*V*dt + d1*dt + d2_i*dW_i - V+/- = V -iH*V*dt + d1*dt +/- d2_i*dt**.5 - The Theory of Open Quantum Systems - Chapter 7 Eq. (7.47), H.-P Breuer, F. Petruccione - """ - cdef int i, j, num_ops = system.num_collapse - cdef double sqrt_dt = np.sqrt(dt) - cdef double sqrt_dt_inv = 0.25 / sqrt_dt - cdef double dw, dw2 - - cdef Data d1 = _data.add(state, system.drift(t, state), dt) - cdef list d2 = system.diffusion(t, state) - cdef Data Vt, out - cdef list Vp, Vm - - out = _data.mul(d1, 0.5) - Vt = d1.copy() - Vp = [] - Vm = [] - for i in range(num_ops): - Vp.append(_data.add(d1, d2[i], sqrt_dt)) - Vm.append(_data.add(d1, d2[i], -sqrt_dt)) - Vt = _data.add(Vt, d2[i], dW[i, 0]) - - d1 = system.drift(t, Vt) - out = _data.add(out, d1, 0.5 * dt) - out = _data.add(out, state, 0.5) - for i in range(num_ops): - d2p = system.diffusion(t, Vp[i]) - d2m = system.diffusion(t, Vm[i]) - dw = dW[i, 0] * 0.25 - out = _data.add(out, d2m[i], dw) - out = _data.add(out, d2[i], 2 * dw) - out = _data.add(out, d2p[i], dw) - - for j in range(num_ops): - dw2 = sqrt_dt_inv * (dW[i, 0] * dW[j, 0] - dt * (i == j)) - out = _data.add(out, d2p[j], dw2) - out = _data.add(out, d2m[j], -dw2) - - return out - - -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) -cpdef Data explicit15(_StochasticSystem system, t, Data state, double dt, double[:, :] dW): - """ - Chapter 11.2 Eq. (2.13) - Numerical Solution of Stochastic Differential Equations - By Peter E. Kloeden, Eckhard Platen - """ - cdef int i, j, k, num_ops = system.num_collapse - cdef double sqrt_dt = np.sqrt(dt) - cdef double sqrt_dt_inv = 1./sqrt_dt - cdef double ddz, ddw, ddd - cdef double[::1] dz, dw - dw = np.empty(num_ops) - dz = np.empty(num_ops) - for i in range(num_ops): - dw[i] = dW[i, 0] - dz[i] = 0.5 *(dW[i, 0] + 1./np.sqrt(3) * dW[i, 1]) - - d1 = system.drift(t, state) - d2 = system.diffusion(t, state) - dd2 = system.diffusion(t + dt, state) - # Euler part - out = _data.add(state, d1, dt) - for i in range(num_ops): - out = _data.add(out, d2[i], dw[i]) - - V = _data.add(state, d1, dt/num_ops) - - v2p = [] - v2m = [] - for i in range(num_ops): - v2p.append(_data.add(V, d2[i], sqrt_dt)) - v2m.append(_data.add(V, d2[i], -sqrt_dt)) - - p2p = [] - p2m = [] - for i in range(num_ops): - d2p = system.diffusion(t, v2p[i]) - d2m = system.diffusion(t, v2m[i]) - ddw = (dw[i] * dw[i] - dt) * 0.25 * sqrt_dt_inv # 1.0 - out = _data.add(out, d2p[i], ddw) - out = _data.add(out, d2m[i], -ddw) - temp_p2p = [] - temp_p2m = [] - for j in range(num_ops): - temp_p2p.append(_data.add(v2p[i], d2p[j], sqrt_dt)) - temp_p2m.append(_data.add(v2p[i], d2p[j], -sqrt_dt)) - p2p.append(temp_p2p) - p2m.append(temp_p2m) - - out = _data.add(out, d1, -0.5*(num_ops) * dt) - - for i in range(num_ops): - ddz = dz[i] * 0.5 / sqrt_dt # 1.5 - ddd = 0.25 * (dw[i] * dw[i] / 3 - dt) * dw[i] / dt # 1.5 - - d1p = system.drift(t + dt/num_ops, v2p[i]) - d1m = system.drift(t + dt/num_ops, v2m[i]) - - d2p = system.diffusion(t, v2p[i]) - d2m = system.diffusion(t, v2m[i]) - d2pp = system.diffusion(t, p2p[i][i]) - d2mm = system.diffusion(t, p2m[i][i]) - - out = _data.add(out, d1p, (0.25 + ddz) * dt) - out = _data.add(out, d1m, (0.25 - ddz) * dt) - - out = _data.add(out, dd2[i], dw[i] - dz[i]) - out = _data.add(out, d2[i], dz[i] - dw[i]) - - out = _data.add(out, d2pp[i], ddd) - out = _data.add(out, d2mm[i], -ddd) - out = _data.add(out, d2p[i], -ddd) - out = _data.add(out, d2m[i], ddd) - - - for j in range(num_ops): - ddw = 0.5 * (dw[j] - dz[j]) # O(1.5) - out = _data.add(out, d2p[j], ddw) - out = _data.add(out, d2[j], -2*ddw) - out = _data.add(out, d2m[j], ddw) - - if j > i: - ddw = 0.5 * (dw[i] * dw[j]) / sqrt_dt # O(1.0) +cdef class Euler: + cdef _StochasticSystem system + + def __init__(self, _StochasticSystem system): + self.system = system + + @cython.wraparound(False) + def run(self, double t, Data state, double dt, double[:, :, ::1] dW, int ntraj): + cdef int i + cdef Dense out + for i in range(ntraj): + out = self.step(t, state, dt, dW[i, :, :]) + state = out + return out + + @cython.boundscheck(False) + @cython.wraparound(False) + cdef step(self, double t, Data state, double dt, double[:, :] dW): + """ + Integration scheme: + Basic Euler order 0.5 + dV = d1 dt + d2_i dW_i + Numerical Solution of Stochastic Differential Equations + By Peter E. Kloeden, Eckhard Platen + """ + cdef int i + cdef _StochasticSystem system = self.system + + a = system.drift(t, state) + b = system.diffusion(t, state) + new_state = _data.add(state, a, dt) + for i in range(system.num_collapse): + new_state = _data.add(new_state, b[i], dW[i, 0]) + return new_state + + +cdef class Platen(Euler): + @cython.boundscheck(False) + @cython.wraparound(False) + @cython.cdivision(True) + cdef step(self, double t, Data state, double dt, double[:, :] dW): + """ + Platen rhs function for both master eq and schrodinger eq. + dV = -iH* (V+Vt)/2 * dt + (d1(V)+d1(Vt))/2 * dt + + (2*d2_i(V)+d2_i(V+)+d2_i(V-))/4 * dW_i + + (d2_i(V+)-d2_i(V-))/4 * (dW_i**2 -dt) * dt**(-.5) + + Vt = V -iH*V*dt + d1*dt + d2_i*dW_i + V+/- = V -iH*V*dt + d1*dt +/- d2_i*dt**.5 + The Theory of Open Quantum Systems + Chapter 7 Eq. (7.47), H.-P Breuer, F. Petruccione + """ + cdef _StochasticSystem system = self.system + cdef int i, j, num_ops = system.num_collapse + cdef double sqrt_dt = np.sqrt(dt) + cdef double sqrt_dt_inv = 0.25 / sqrt_dt + cdef double dw, dw2 + + cdef Data d1 = _data.add(state, system.drift(t, state), dt) + cdef list d2 = system.diffusion(t, state) + cdef Data Vt, out + cdef list Vp, Vm + + out = _data.mul(d1, 0.5) + Vt = d1.copy() + Vp = [] + Vm = [] + for i in range(num_ops): + Vp.append(_data.add(d1, d2[i], sqrt_dt)) + Vm.append(_data.add(d1, d2[i], -sqrt_dt)) + Vt = _data.add(Vt, d2[i], dW[i, 0]) + + d1 = system.drift(t, Vt) + out = _data.add(out, d1, 0.5 * dt) + out = _data.add(out, state, 0.5) + for i in range(num_ops): + d2p = system.diffusion(t, Vp[i]) + d2m = system.diffusion(t, Vm[i]) + dw = dW[i, 0] * 0.25 + out = _data.add(out, d2m[i], dw) + out = _data.add(out, d2[i], 2 * dw) + out = _data.add(out, d2p[i], dw) + + for j in range(num_ops): + dw2 = sqrt_dt_inv * (dW[i, 0] * dW[j, 0] - dt * (i == j)) + out = _data.add(out, d2p[j], dw2) + out = _data.add(out, d2m[j], -dw2) + + return out + + +cdef class Explicit15(Euler): + @cython.boundscheck(False) + @cython.wraparound(False) + @cython.cdivision(True) + cdef step(self, double t, Data state, double dt, double[:, :] dW): + """ + Chapter 11.2 Eq. (2.13) + Numerical Solution of Stochastic Differential Equations + By Peter E. Kloeden, Eckhard Platen + """ + cdef _StochasticSystem system = self.system + cdef int i, j, k, num_ops = system.num_collapse + cdef double sqrt_dt = np.sqrt(dt) + cdef double sqrt_dt_inv = 1./sqrt_dt + cdef double ddz, ddw, ddd + cdef double[::1] dz, dw + + dw = np.empty(num_ops) + dz = np.empty(num_ops) + for i in range(num_ops): + dw[i] = dW[i, 0] + dz[i] = 0.5 *(dW[i, 0] + 1./np.sqrt(3) * dW[i, 1]) + + d1 = system.drift(t, state) + d2 = system.diffusion(t, state) + dd2 = system.diffusion(t + dt, state) + # Euler part + out = _data.add(state, d1, dt) + for i in range(num_ops): + out = _data.add(out, d2[i], dw[i]) + + V = _data.add(state, d1, dt/num_ops) + + v2p = [] + v2m = [] + for i in range(num_ops): + v2p.append(_data.add(V, d2[i], sqrt_dt)) + v2m.append(_data.add(V, d2[i], -sqrt_dt)) + + p2p = [] + p2m = [] + for i in range(num_ops): + d2p = system.diffusion(t, v2p[i]) + d2m = system.diffusion(t, v2m[i]) + ddw = (dw[i] * dw[i] - dt) * 0.25 * sqrt_dt_inv # 1.0 + out = _data.add(out, d2p[i], ddw) + out = _data.add(out, d2m[i], -ddw) + temp_p2p = [] + temp_p2m = [] + for j in range(num_ops): + temp_p2p.append(_data.add(v2p[i], d2p[j], sqrt_dt)) + temp_p2m.append(_data.add(v2p[i], d2p[j], -sqrt_dt)) + p2p.append(temp_p2p) + p2m.append(temp_p2m) + + out = _data.add(out, d1, -0.5*(num_ops) * dt) + + for i in range(num_ops): + ddz = dz[i] * 0.5 / sqrt_dt # 1.5 + ddd = 0.25 * (dw[i] * dw[i] / 3 - dt) * dw[i] / dt # 1.5 + + d1p = system.drift(t + dt/num_ops, v2p[i]) + d1m = system.drift(t + dt/num_ops, v2m[i]) + + d2p = system.diffusion(t, v2p[i]) + d2m = system.diffusion(t, v2m[i]) + d2pp = system.diffusion(t, p2p[i][i]) + d2mm = system.diffusion(t, p2m[i][i]) + + out = _data.add(out, d1p, (0.25 + ddz) * dt) + out = _data.add(out, d1m, (0.25 - ddz) * dt) + + out = _data.add(out, dd2[i], dw[i] - dz[i]) + out = _data.add(out, d2[i], dz[i] - dw[i]) + + out = _data.add(out, d2pp[i], ddd) + out = _data.add(out, d2mm[i], -ddd) + out = _data.add(out, d2p[i], -ddd) + out = _data.add(out, d2m[i], ddd) + + for j in range(num_ops): + ddw = 0.5 * (dw[j] - dz[j]) # O(1.5) out = _data.add(out, d2p[j], ddw) - out = _data.add(out, d2m[j], -ddw) - - ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt # O(1.5) - d2pp = system.diffusion(t, p2p[j][i]) - d2mm = system.diffusion(t, p2m[j][i]) - - out = _data.add(out, d2pp[j], ddw) - out = _data.add(out, d2mm[j], -ddw) - out = _data.add(out, d2p[j], -ddw) + out = _data.add(out, d2[j], -2*ddw) out = _data.add(out, d2m[j], ddw) + if j > i: + ddw = 0.5 * (dw[i] * dw[j]) / sqrt_dt # O(1.0) + out = _data.add(out, d2p[j], ddw) + out = _data.add(out, d2m[j], -ddw) + + ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt # O(1.5) + d2pp = system.diffusion(t, p2p[j][i]) + d2mm = system.diffusion(t, p2m[j][i]) + + out = _data.add(out, d2pp[j], ddw) + out = _data.add(out, d2mm[j], -ddw) + out = _data.add(out, d2p[j], -ddw) + out = _data.add(out, d2m[j], ddw) + + for k in range(j+1, num_ops): + ddw = 0.5 * dw[i] * dw[j] * dw[k] / dt # O(1.5) + + out = _data.add(out, d2pp[k], ddw) + out = _data.add(out, d2mm[k], -ddw) + out = _data.add(out, d2p[k], -ddw) + out = _data.add(out, d2m[k], ddw) + + if j < i: + ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt # O(1.5) + + d2pp = system.diffusion(t, p2p[j][i]) + d2mm = system.diffusion(t, p2m[j][i]) + + out = _data.add(out, d2pp[j], ddw) + out = _data.add(out, d2mm[j], -ddw) + out = _data.add(out, d2p[j], -ddw) + out = _data.add(out, d2m[j], ddw) + + return out + + +cdef class Milstein: + cdef _StochasticSystem system + + def __init__(self, _StochasticSystem system): + self.system = system + + @cython.wraparound(False) + def run(self, double t, Data state, double dt, double[:, :, ::1] dW, int ntraj): + cdef int i + if type(state) != _data.Dense: + state = _data.to(_data.Dense, state) + cdef Dense out = _data.mul_dense(state, 0) + state = state.copy() + + for i in range(ntraj): + self.step(t, state, dt, dW[i, :, :], out) + state, out = out, state + return state + + @cython.boundscheck(False) + @cython.wraparound(False) + cdef step(self, double t, Dense state, double dt, double[:, :] dW, Dense out): + """ + Chapter 10.3 Eq. (3.1) + Numerical Solution of Stochastic Differential Equations + By Peter E. Kloeden, Eckhard Platen + + dV = -iH*V*dt + d1*dt + d2_i*dW_i + + 0.5*d2_i' d2_j*(dW_i*dw_j -dt*delta_ij) + """ + cdef _StochasticSystem system = self.system + cdef int i, j, num_ops = system.num_collapse + cdef double dw + + system.set_state(t, state) + + _data.imul_dense(out, 0.) + _data.iadd_dense(out, state, 1) + _data.iadd_dense(out, system.a(), dt) + + for i in range(num_ops): + _data.iadd_dense(out, system.bi(i), dW[i, 0]) + + for i in range(num_ops): + for j in range(i, num_ops): + if i == j: + dw = (dW[i, 0] * dW[j, 0] - dt) * 0.5 + else: + dw = dW[i, 0] * dW[j, 0] + _data.iadd_dense(out, system.Libj(i, j), dw) + + +cdef class PredCorr: + cdef Dense euler + cdef double alpha, eta + cdef _StochasticSystem system + + def __init__(self, _StochasticSystem system, double alpha=0., double eta=0.5): + self.system = system + self.alpha = alpha + self.eta = eta + + @cython.wraparound(False) + def run(self, double t, Data state, double dt, double[:, :, ::1] dW, int ntraj): + cdef int i + if type(state) != _data.Dense: + state = _data.to(_data.Dense, state) + cdef Dense out = _data.mul_dense(state, 0) + self.euler = _data.mul_dense(state, 0) + state = state.copy() + + for i in range(ntraj): + self.step(t, state, dt, dW[i, :, :], out) + state, out = out, state + return state + + @cython.boundscheck(False) + @cython.wraparound(False) + cdef step(self, double t, Dense state, double dt, double[:, :] dW, Dense out): + """ + Chapter 10.3 Eq. (3.1) + Numerical Solution of Stochastic Differential Equations + By Peter E. Kloeden, Eckhard Platen + + dV = -iH*V*dt + d1*dt + d2_i*dW_i + + 0.5*d2_i' d2_j*(dW_i*dw_j -dt*delta_ij) + """ + cdef _StochasticSystem system = self.system + cdef int i, j, k, num_ops = system.num_collapse + cdef double eta=self.eta, alpha=self.alpha + cdef Dense euler = self.euler + + system.set_state(t, state) + + _data.imul_dense(out, 0.) + _data.iadd_dense(out, state, 1) + _data.iadd_dense(out, system.a(), dt*(1-alpha)) + + _data.imul_dense(euler, 0.) + _data.iadd_dense(euler, state, 1) + _data.iadd_dense(euler, system.a(), dt) + + for i in range(num_ops): + _data.iadd_dense(euler, system.bi(i), dW[i, 0]) + _data.iadd_dense(out, system.bi(i), dW[i, 0]*eta) + _data.iadd_dense(out, system.Libj(i, i), dt*(alpha-1)*0.5) + + system.set_state(t+dt, euler) + if alpha: + _data.iadd_dense(out, system.a(), dt*alpha) + + for i in range(num_ops): + _data.iadd_dense(out, system.bi(i), dW[i, 0]*(1-eta)) + _data.iadd_dense(out, system.Libj(i, i), -dt*alpha*0.5) + + return out + + +cdef class Taylor15(Milstein): + @cython.wraparound(False) + def run(self, double t, Data state, double dt, double[:, :, ::1] dW, int ntraj): + cdef int i + if type(state) != _data.Dense: + state = _data.to(_data.Dense, state) + cdef Dense out = _data.mul_dense(state, 0) + state = state.copy() + + for i in range(ntraj): + self.step(t, state, dt, dW[i, :, :], out) + state, out = out, state + return state + + @cython.boundscheck(False) + @cython.wraparound(False) + cdef step(self, double t, Dense state, double dt, double[:, :] dW, Dense out): + """ + Chapter 12.2 Eq. (2.18), + Numerical Solution of Stochastic Differential Equations + By Peter E. Kloeden, Eckhard Platen + """ + cdef _StochasticSystem system = self.system + system.set_state(t, state) + cdef int i, j, k, num_ops = system.num_collapse + cdef double[:] dz, dw + + num_ops = system.num_collapse + dw = dW[:, 0] + dz = 0.5 *(dW[:, 0] + 1./np.sqrt(3) * dW[:, 1]) * dt + + _data.imul_dense(out, 0.) + _data.iadd_dense(out, state, 1) + _data.iadd_dense(out, system.a(), dt) + _data.iadd_dense(out, system.L0a(), 0.5 * dt * dt) + + for i in range(num_ops): + _data.iadd_dense(out, system.bi(i), dw[i]) + _data.iadd_dense(out, system.Libj(i, i), 0.5 * (dw[i] * dw[i] - dt)) + _data.iadd_dense(out, system.Lia(i), dz[i]) + _data.iadd_dense(out, system.L0bi(i), dw[i] * dt - dz[i]) + _data.iadd_dense(out, system.LiLjbk(i, i, i), + 0.5 * ((1/3.) * dw[i] * dw[i] - dt) * dw[i]) + + for j in range(i+1, num_ops): + _data.iadd_dense(out, system.Libj(i, j), dw[i] * dw[j]) + _data.iadd_dense(out, system.LiLjbk(i, j, j), 0.5 * (dw[j] * dw[j] -dt) * dw[i]) + _data.iadd_dense(out, system.LiLjbk(i, i, j), 0.5 * (dw[i] * dw[i] -dt) * dw[j]) for k in range(j+1, num_ops): - ddw = 0.5 * dw[i] * dw[j] * dw[k] / dt # O(1.5) - - out = _data.add(out, d2pp[k], ddw) - out = _data.add(out, d2mm[k], -ddw) - out = _data.add(out, d2p[k], -ddw) - out = _data.add(out, d2m[k], ddw) - - if j < i: - ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt # O(1.5) - - d2pp = system.diffusion(t, p2p[j][i]) - d2mm = system.diffusion(t, p2m[j][i]) - - out = _data.add(out, d2pp[j], ddw) - out = _data.add(out, d2mm[j], -ddw) - out = _data.add(out, d2p[j], -ddw) - out = _data.add(out, d2m[j], ddw) - - return out - - - -cpdef Data milstein(_StochasticSystem system, t, Data state, double dt, double[:, :] dW): - """ - Chapter 10.3 Eq. (3.1) - Numerical Solution of Stochastic Differential Equations - By Peter E. Kloeden, Eckhard Platen - - dV = -iH*V*dt + d1*dt + d2_i*dW_i - + 0.5*d2_i' d2_j*(dW_i*dw_j -dt*delta_ij) - """ - cdef int i, j, num_ops = system.num_collapse - cdef double dw - - system.set_state(t, state) - - out = _data.add_dense(state, system.a(), dt) - - for i in range(num_ops): - _data.iadd_dense(out, system.bi(i), dW[i, 0]) - - for i in range(num_ops): - for j in range(i, num_ops): - dw = (dW[i, 0] * dW[j, 0] - dt * (i == j)) * (0.5 + (i != j) * 0.5) - _data.iadd_dense(out, system.Libj(i, j), dw) - - return out - - -cpdef Data pred_corr(_StochasticSystem system, t, Data state, double dt, double[:, :] dW, alpha=0.): - """ - Chapter 10.3 Eq. (3.1) - Numerical Solution of Stochastic Differential Equations - By Peter E. Kloeden, Eckhard Platen - - dV = -iH*V*dt + d1*dt + d2_i*dW_i - + 0.5*d2_i' d2_j*(dW_i*dw_j -dt*delta_ij) - """ - cdef int i, j, k, num_ops = system.num_collapse - cdef double eta=0.5 - - system.set_state(t, state) - - out = _data.add_dense(state, system.a(), dt*(1-alpha)) - euler = _data.add_dense(state, system.a(), dt) - - for i in range(num_ops): - _data.iadd_dense(euler, system.bi(i), dW[i, 0]) - _data.iadd_dense(out, system.bi(i), dW[i, 0]*eta) - _data.iadd_dense(out, system.Libj(i, i), dt*(alpha-1)*0.5) - - system.set_state(t+dt, euler) - if alpha: - _data.iadd_dense(out, system.a(), dt*alpha) - - for i in range(num_ops): - _data.iadd_dense(out, system.bi(i), dW[i, 0]*(1-eta)) - _data.iadd_dense(out, system.Libj(i, i), -dt*alpha*0.5) - - return out - - -cpdef Data taylor15(_StochasticSystem system, t, Data state, double dt, double[:, :] dW): - """ - Chapter 12.2 Eq. (2.18), - Numerical Solution of Stochastic Differential Equations - By Peter E. Kloeden, Eckhard Platen - """ - system.set_state(t, state) - cdef int i, j, k, num_ops = system.num_collapse - cdef double[:] dz, dw - - num_ops = system.num_collapse - dw = dW[:, 0] - dz = 0.5 *(dW[:, 0] + 1./np.sqrt(3) * dW[:, 1]) * dt - - out = _data.add_dense(state, system.a(), dt) - _data.iadd_dense(out, system.L0a(), 0.5 * dt*dt) - - for i in range(num_ops): - _data.iadd_dense(out, system.bi(i), dw[i]) - _data.iadd_dense(out, system.Libj(i, i), 0.5*(dw[i]*dw[i]-dt)) - _data.iadd_dense(out, system.Lia(i), dz[i]) - _data.iadd_dense(out, system.L0bi(i), dw[i] * dt - dz[i]) - _data.iadd_dense(out, system.LiLjbk(i, i, i), - 0.5 * ((1/3.) * dw[i] * dw[i] - dt) * dw[i]) - - for j in range(i+1, num_ops): - _data.iadd_dense(out, system.Libj(i, j), dw[i]*dw[j]) - _data.iadd_dense(out, system.LiLjbk(i, j, j), 0.5*(dw[j]*dw[j]-dt)*dw[i]) - _data.iadd_dense(out, system.LiLjbk(i, i, j), 0.5*(dw[i]*dw[i]-dt)*dw[j]) - for k in range(j+1, num_ops): - _data.iadd_dense(out, system.LiLjbk(i, j, k), dw[i]*dw[j]*dw[k]) + _data.iadd_dense(out, system.LiLjbk(i, j, k), dw[i]*dw[j]*dw[k]) - return out + return out diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 5b90b312aa..088af4c1d3 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -104,11 +104,7 @@ def __init__(self, system, options): self.dt = self.options["dt"] self.tol = self.options["tol"] self.N_drift = system.num_collapse - - def _step(self, dt, dW): - new_state = self.stepper(self.system, self.t, self.state, dt, dW) - self.state = new_state - self.t += dt + self.step_func = self.stepper(self.system).run def set_state(self, t, state0, generator): self.t = t @@ -135,8 +131,8 @@ def integrate(self, t, copy=True): size=(N, self.N_drift, self.N_dw) ) - for i in range(N): - self._step(dt, dW[i, :]) + self.state = self.step_func(self.t, self.state, dt, dW, N) + self.t += dt * N return self.t, self.state, np.sum(dW, axis=0) / (N * dt) @@ -169,7 +165,7 @@ class EulerSODE(_Explicit_Simple_Integrator): - Order: 0.5 """ - stepper = _sode.euler + stepper = _sode.Euler N_dw = 1 @@ -183,7 +179,7 @@ class PlatenSODE(_Explicit_Simple_Integrator): - Order: strong 1, weak 2 """ - stepper = _sode.platen + stepper = _sode.Platen N_dw = 1 @@ -198,7 +194,7 @@ class Explicit1_5_SODE(_Explicit_Simple_Integrator): - Order: strong 1.5 """ - stepper = _sode.explicit15 + stepper = _sode.Explicit15 N_dw = 2 @@ -211,7 +207,7 @@ class Taylor1_5_SODE(_Explicit_Simple_Integrator): - Order strong 1.5 """ - stepper = _sode.taylor15 + stepper = _sode.Taylor15 N_dw = 2 @@ -224,7 +220,7 @@ class Milstein_SODE(_Explicit_Simple_Integrator): - Order strong 1.0 """ - stepper = _sode.milstein + stepper = _sode.Milstein N_dw = 1 @@ -243,7 +239,7 @@ class PredCorr_SODE(_Explicit_Simple_Integrator): (:math:`\\alpha=1/2`, :math:`\\eta=1/2`): ``'pc-euler-imp'``, ``'pc-euler-2'`` or ``'pred-corr-2'`` """ - stepper = _sode.pred_corr + stepper = _sode.PredCorr N_dw = 1 From b33e3323e370d9c8d31590b195a1ea4b0abaa2b3 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Thu, 2 Feb 2023 19:53:22 +0900 Subject: [PATCH 034/602] Fixed imports --- qutip/solver/mcsolve.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index afab97a3b5..6779bdd8ab 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -3,10 +3,9 @@ import warnings import numpy as np -from copy import copy -from ..core import QobjEvo, spre, spost, Qobj, unstack_columns, liouvillian +from ..core import QobjEvo, spre, spost, Qobj, unstack_columns from .multitraj import MultiTrajSolver -from .solver_base import Solver +from .solver_base import Solver, Integrator from .result import McResult, Result from .mesolve import mesolve, MESolver import qutip.core.data as _data From 711fabd89fe3faf019a2180b4bba40df4527b6d2 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Thu, 2 Feb 2023 19:57:21 +0900 Subject: [PATCH 035/602] Added time parameter to _restore_state method in all solvers --- qutip/solver/floquet.py | 5 +++-- qutip/solver/heom/bofin_solvers.py | 2 +- qutip/solver/mcsolve.py | 6 +++--- qutip/solver/multitraj.py | 8 ++++---- qutip/solver/solver_base.py | 11 ++++++----- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/qutip/solver/floquet.py b/qutip/solver/floquet.py index 25fca8aff7..8d36086940 100644 --- a/qutip/solver/floquet.py +++ b/qutip/solver/floquet.py @@ -916,14 +916,15 @@ def run(self, state0, tlist, *, floquet=False, args=None, e_ops=None): stats=stats, floquet_basis=self.floquet_basis, ) - results.add(tlist[0], self._restore_state(_data0, copy=False)) + results.add(tlist[0], + self._restore_state(_data0, tlist[0], copy=False)) stats["preparation time"] += time() - _time_start progress_bar = progess_bars[self.options["progress_bar"]]() progress_bar.start(len(tlist) - 1, **self.options["progress_kwargs"]) for t, state in self._integrator.run(tlist): progress_bar.update() - results.add(t, self._restore_state(state, copy=False)) + results.add(t, self._restore_state(state, t, copy=False)) progress_bar.finished() stats["run time"] = progress_bar.total_time() diff --git a/qutip/solver/heom/bofin_solvers.py b/qutip/solver/heom/bofin_solvers.py index 99131ae990..184937092e 100644 --- a/qutip/solver/heom/bofin_solvers.py +++ b/qutip/solver/heom/bofin_solvers.py @@ -1089,7 +1089,7 @@ def _prepare_state(self, state): return rho0_he - def _restore_state(self, state, *, copy=True): + def _restore_state(self, state, t, *, copy=True): n = self._sys_shape rho_shape = (n, n) rho_dims = self._sys_dims diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 6779bdd8ab..aa0d5b953a 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -394,7 +394,7 @@ def __init__(self, H, c_ops, *, options=None): super().__init__(rhs, options=options) - def _restore_state(self, data, *, copy=True): + def _restore_state(self, data, t, *, copy=True): """ Retore the Qobj state from its data. """ @@ -434,10 +434,10 @@ def _run_one_traj(self, seed, state, tlist, e_ops): result = Result(e_ops, {**self.options, "normalize_output": False}) generator = self._get_generator(seed) self._integrator.set_state(tlist[0], state, generator) - result.add(tlist[0], self._restore_state(state, copy=False)) + result.add(tlist[0], self._restore_state(state, tlist[0], copy=False)) for t in tlist[1:]: t, state = self._integrator.integrate(t, copy=False) - result.add(t, self._restore_state(state, copy=False)) + result.add(t, self._restore_state(state, t, copy=False)) result.collapse = self._integrator.collapses return seed, result diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 73f84de66b..7f1227d9ab 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -101,8 +101,8 @@ def step(self, t, *, args=None, copy=True): if not self._integrator._is_set: raise RuntimeError("The `start` method must called first.") self._argument(args) - _, state = self._integrator.integrate(t, copy=False) - return self._restore_state(state, copy=copy) + t, state = self._integrator.integrate(t, copy=False) + return self._restore_state(state, t, copy=copy) def run(self, state, tlist, ntraj=1, *, args=None, e_ops=(), timeout=None, target_tol=None, seed=None): @@ -202,10 +202,10 @@ def _run_one_traj(self, seed, state, tlist, e_ops): result = Result(e_ops, self.options) generator = self._get_generator(seed) self._integrator.set_state(tlist[0], state, generator) - result.add(tlist[0], self._restore_state(state, copy=False)) + result.add(tlist[0], self._restore_state(state, tlist[0], copy=False)) for t in tlist[1:]: t, state = self._integrator.step(t, copy=False) - result.add(t, self._restore_state(state, copy=False)) + result.add(t, self._restore_state(state, t, copy=False)) return seed, result def _read_seed(self, seed, ntraj): diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index 8c08cea223..520e483fcf 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -90,7 +90,7 @@ def _prepare_state(self, state): return stack_columns(state.data) return state.data - def _restore_state(self, data, *, copy=True): + def _restore_state(self, data, t, *, copy=True): """ Retore the Qobj state from its data. """ @@ -148,14 +148,15 @@ def run(self, state0, tlist, *, args=None, e_ops=None): e_ops, self.options, solver=self.name, stats=stats, ) - results.add(tlist[0], self._restore_state(_data0, copy=False)) + results.add(tlist[0], + self._restore_state(_data0, tlist[0], copy=False)) stats['preparation time'] += time() - _time_start progress_bar = progess_bars[self.options['progress_bar']]() progress_bar.start(len(tlist)-1, **self.options['progress_kwargs']) for t, state in self._integrator.run(tlist): progress_bar.update() - results.add(t, self._restore_state(state, copy=False)) + results.add(t, self._restore_state(state, t, copy=False)) progress_bar.finished() stats['run time'] = progress_bar.total_time() @@ -206,9 +207,9 @@ def step(self, t, *, args=None, copy=True): raise RuntimeError("The `start` method must called first") _time_start = time() self._argument(args) - _, state = self._integrator.integrate(t, copy=False) + t, state = self._integrator.integrate(t, copy=False) self.stats["run time"] += time() - _time_start - return self._restore_state(state, copy=copy) + return self._restore_state(state, t, copy=copy) def _get_integrator(self): """ Return the initialted integrator. """ From faee1be4f3cacd91fbbf70532b0a009203807685 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Thu, 2 Feb 2023 19:58:04 +0900 Subject: [PATCH 036/602] Non-Markovian MCSolver: Monte-Carlo simulations for Lindblad equations with negative rates --- qutip/solver/__init__.py | 1 + qutip/solver/nm_mcsolve.py | 103 +++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 qutip/solver/nm_mcsolve.py diff --git a/qutip/solver/__init__.py b/qutip/solver/__init__.py index 855bb29d15..26c9866f3a 100644 --- a/qutip/solver/__init__.py +++ b/qutip/solver/__init__.py @@ -5,6 +5,7 @@ from .sesolve import * from .mesolve import * from .mcsolve import * +from .nm_mcsolve import * from .propagator import * from .scattering import * from .correlation import * diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py new file mode 100644 index 0000000000..ec706526ca --- /dev/null +++ b/qutip/solver/nm_mcsolve.py @@ -0,0 +1,103 @@ +__all__ = ['NonMarkovianMCSolver'] + +import numpy as np +import scipy +from .mcsolve import MCSolver +from ..core import QobjEvo, isket, ket2dm + + +class NonMarkovianMCSolver(MCSolver): + r""" + Monte-Carlo solver for master equation with negative rates. + Based on the methods explained in arXiv:2209.08958 [quant-ph] + """ + + # ops_and_rates is a list of tuples (L_i, Gamma_i), where Gamma_i = Gamma_i(t) is callable + def __init__(self, H, ops_and_rates, *args, + completeness_rtol=None, completeness_atol=None, **kwargs): + self.ops_and_rates = list(ops_and_rates) + self._a_parameter, L = self._check_completeness(ops_and_rates, + completeness_rtol, + completeness_atol) + if L is not None: + self.ops_and_rates.append((L, lambda t: 0)) + + c_ops = self._compute_paired_c_ops() + super().__init__(H, c_ops, *args, **kwargs) + + # Check whether op = sum(Li.dag() * Li) is proportional to identity + # If not, creates an extra Lindblad operator so that it is + # Returns: * the proportionality factor a + # * the extra Lindblad operator (or None if not necessary) + @staticmethod + def _check_completeness(ops_and_rates, + completeness_rtol=None, completeness_atol=None): + tolerance_settings = {} + if completeness_rtol is not None: + tolerance_settings['rtol'] = completeness_rtol + if completeness_atol is not None: + tolerance_settings['atol'] = completeness_atol + + op = sum((f[0].dag() * f[0]).full() for f in ops_and_rates) + + a_candidate = op[0, 0] + if np.allclose(a_candidate * np.eye(len(op)), op, + **tolerance_settings): + return np.real(a_candidate), None + + w, _ = np.linalg.eig(op) + a = max(np.real(w)) + L = scipy.linalg.sqrtm(a * np.eye(len(op)) - op) # new Lindblad operator + return a, L + + # Shifts all rate function by the function rate_shift + # Returns c_i = L_i * sqrt(gamma_i) as QobjEvo objects + def _compute_paired_c_ops(self): + c_ops = [] + for f in self.ops_and_rates: + def sqrt_gamma(t, f=f): + return np.sqrt(f[1](t) + self._rate_shift(t)) + c_ops.append(QobjEvo([f[0], sqrt_gamma])) + return c_ops + + def _rate_shift(self, t): + min_rate = min(f[1](t) for f in self.ops_and_rates) + return 2 * abs(min(0, min_rate)) + + # Continuous part of the martingale evolution + def _continuous_martingale(self, tlist): + mu_c = {t: 1 for t in tlist} + integral = 0 + for t1, t2 in zip(tlist, tlist[1:]): + # We compute the time integral for every interval and sum them together + integral += scipy.integrate.quad(self._rate_shift, t1, t2)[0] + mu_c[t2] = np.exp(self._a_parameter * integral) + return mu_c + + # Discrete part of the martingale evolution + # collapses is a list of (t_k, i_k) + def _discrete_martingale(self, collapses): + o_r = self.ops_and_rates + factors = [o_r[ik][1](tk) / (o_r[ik][1](tk) + self._rate_shift(tk)) + for tk, ik in collapses] + return np.prod(factors) + + # Override "run" to initialize continuous part of martingale evolution + def run(self, state, tlist, *args, **kwargs): + self._mu_c = self._continuous_martingale(tlist) + return super().run(state, tlist, *args, **kwargs) + + # Override "_restore_state" to include the martingale in the state + def _restore_state(self, data, time, *, copy=True): + # find state |psi> Date: Fri, 3 Feb 2023 11:24:33 +0900 Subject: [PATCH 037/602] Fixed issue related to trying to pickle an inner function --- qutip/solver/nm_mcsolve.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index ec706526ca..f2ce33abbe 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -1,5 +1,6 @@ __all__ = ['NonMarkovianMCSolver'] +from functools import partial import numpy as np import scipy from .mcsolve import MCSolver @@ -55,14 +56,16 @@ def _check_completeness(ops_and_rates, def _compute_paired_c_ops(self): c_ops = [] for f in self.ops_and_rates: - def sqrt_gamma(t, f=f): - return np.sqrt(f[1](t) + self._rate_shift(t)) + sqrt_gamma = partial(self._sqrt_gamma, original_rate=f[1]) c_ops.append(QobjEvo([f[0], sqrt_gamma])) return c_ops def _rate_shift(self, t): min_rate = min(f[1](t) for f in self.ops_and_rates) return 2 * abs(min(0, min_rate)) + + def _sqrt_gamma(self, t, original_rate): + return np.sqrt(original_rate(t) + self._rate_shift(t)) # Continuous part of the martingale evolution def _continuous_martingale(self, tlist): @@ -88,16 +91,16 @@ def run(self, state, tlist, *args, **kwargs): return super().run(state, tlist, *args, **kwargs) # Override "_restore_state" to include the martingale in the state - def _restore_state(self, data, time, *, copy=True): + def _restore_state(self, data, t, *, copy=True): # find state |psi> Date: Fri, 3 Feb 2023 11:50:58 +0900 Subject: [PATCH 038/602] Added completeness_rtol / completeness_atol as options instead of class parameters --- qutip/solver/nm_mcsolve.py | 46 ++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index f2ce33abbe..57a30d9108 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -4,6 +4,7 @@ import numpy as np import scipy from .mcsolve import MCSolver +from .result import McResult from ..core import QobjEvo, isket, ket2dm @@ -12,32 +13,53 @@ class NonMarkovianMCSolver(MCSolver): Monte-Carlo solver for master equation with negative rates. Based on the methods explained in arXiv:2209.08958 [quant-ph] """ + name = "nm_mcsolve" + resultclass = McResult + solver_options = { + "progress_bar": "text", + "progress_kwargs": {"chunk_size": 10}, + "store_final_state": False, + "store_states": None, + "keep_runs_results": False, + "method": "adams", + "map": "serial", + "job_timeout": None, + "num_cpus": None, + "bitgenerator": None, + "mc_corr_eps": 1e-10, + "norm_steps": 5, + "norm_t_tol": 1e-6, + "norm_tol": 1e-4, + "completeness_rtol": None, + "completeness_atol": None, + } # ops_and_rates is a list of tuples (L_i, Gamma_i), where Gamma_i = Gamma_i(t) is callable - def __init__(self, H, ops_and_rates, *args, - completeness_rtol=None, completeness_atol=None, **kwargs): + def __init__(self, H, ops_and_rates, *args, options=None, **kwargs): self.ops_and_rates = list(ops_and_rates) - self._a_parameter, L = self._check_completeness(ops_and_rates, - completeness_rtol, - completeness_atol) + + self._a_parameter, L = self._check_completeness(ops_and_rates, options) if L is not None: self.ops_and_rates.append((L, lambda t: 0)) c_ops = self._compute_paired_c_ops() - super().__init__(H, c_ops, *args, **kwargs) + super().__init__(H, c_ops, *args, options=options, **kwargs) # Check whether op = sum(Li.dag() * Li) is proportional to identity # If not, creates an extra Lindblad operator so that it is # Returns: * the proportionality factor a # * the extra Lindblad operator (or None if not necessary) @staticmethod - def _check_completeness(ops_and_rates, - completeness_rtol=None, completeness_atol=None): + def _check_completeness(ops_and_rates, options=None): tolerance_settings = {} - if completeness_rtol is not None: - tolerance_settings['rtol'] = completeness_rtol - if completeness_atol is not None: - tolerance_settings['atol'] = completeness_atol + if options is not None: + rtol = options.get('completeness_rtol', None) + if rtol is not None: + tolerance_settings['rtol'] = rtol + + atol = options.get('completeness_atol', None) + if atol is not None: + tolerance_settings['atol'] = atol op = sum((f[0].dag() * f[0]).full() for f in ops_and_rates) From 85b9a9b39956c8ea50c99d76e9b9d9664aabd872 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 3 Feb 2023 12:02:21 +0900 Subject: [PATCH 039/602] Bugfixes related to (N+1)st Lindblad operator --- qutip/solver/nm_mcsolve.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 57a30d9108..17748ba3ae 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -5,7 +5,7 @@ import scipy from .mcsolve import MCSolver from .result import McResult -from ..core import QobjEvo, isket, ket2dm +from ..core import Qobj, QobjEvo, isket, ket2dm class NonMarkovianMCSolver(MCSolver): @@ -40,7 +40,7 @@ def __init__(self, H, ops_and_rates, *args, options=None, **kwargs): self._a_parameter, L = self._check_completeness(ops_and_rates, options) if L is not None: - self.ops_and_rates.append((L, lambda t: 0)) + self.ops_and_rates.append((L, float)) # float() is zero c_ops = self._compute_paired_c_ops() super().__init__(H, c_ops, *args, options=options, **kwargs) @@ -71,7 +71,7 @@ def _check_completeness(ops_and_rates, options=None): w, _ = np.linalg.eig(op) a = max(np.real(w)) L = scipy.linalg.sqrtm(a * np.eye(len(op)) - op) # new Lindblad operator - return a, L + return a, Qobj(L) # Shifts all rate function by the function rate_shift # Returns c_i = L_i * sqrt(gamma_i) as QobjEvo objects From 16141d9d48bd2009e0805267ee04147a4a9df151 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 3 Feb 2023 13:40:42 +0900 Subject: [PATCH 040/602] Modified calculation of continuous martingale part so that both run and start/step will work --- qutip/solver/nm_mcsolve.py | 46 +++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 17748ba3ae..69a5787e15 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -37,10 +37,11 @@ class NonMarkovianMCSolver(MCSolver): # ops_and_rates is a list of tuples (L_i, Gamma_i), where Gamma_i = Gamma_i(t) is callable def __init__(self, H, ops_and_rates, *args, options=None, **kwargs): self.ops_and_rates = list(ops_and_rates) + self._mu_c = None self._a_parameter, L = self._check_completeness(ops_and_rates, options) if L is not None: - self.ops_and_rates.append((L, float)) # float() is zero + self.ops_and_rates.append((L, float)) # float() is zero c_ops = self._compute_paired_c_ops() super().__init__(H, c_ops, *args, options=options, **kwargs) @@ -85,19 +86,31 @@ def _compute_paired_c_ops(self): def _rate_shift(self, t): min_rate = min(f[1](t) for f in self.ops_and_rates) return 2 * abs(min(0, min_rate)) - + def _sqrt_gamma(self, t, original_rate): return np.sqrt(original_rate(t) + self._rate_shift(t)) # Continuous part of the martingale evolution - def _continuous_martingale(self, tlist): - mu_c = {t: 1 for t in tlist} - integral = 0 - for t1, t2 in zip(tlist, tlist[1:]): - # We compute the time integral for every interval and sum them together - integral += scipy.integrate.quad(self._rate_shift, t1, t2)[0] - mu_c[t2] = np.exp(self._a_parameter * integral) - return mu_c + # Checks self._mu_c for the closest time t0 earlier than the given time t + # and starts integration from there. + # Returns the continuous part of the martingale at time t and stores it in + # self._mu_c + def _continuous_martingale(self, t): + if self._mu_c is None: + raise RuntimeError("The `start` method must called first.") + if t in self._mu_c: + return self._mu_c[t] + + earlier_times = filter(lambda t0: t0 < t, self._mu_c.keys()) + try: + t0 = max(earlier_times) + except ValueError as exc: + raise ValueError("Cannot integrate backwards in time.") from exc + + integral = scipy.integrate.quad(self._rate_shift, t0, t)[0] + result = self._mu_c[t0] * np.exp(self._a_parameter * integral) + self._mu_c[t] = result + return result # Discrete part of the martingale evolution # collapses is a list of (t_k, i_k) @@ -107,11 +120,17 @@ def _discrete_martingale(self, collapses): for tk, ik in collapses] return np.prod(factors) - # Override "run" to initialize continuous part of martingale evolution + # Override "run" and "start" to initialize continuous part of martingale evolution def run(self, state, tlist, *args, **kwargs): - self._mu_c = self._continuous_martingale(tlist) + self._mu_c = {tlist[0]: 1} + for t in tlist[1:]: + self._continuous_martingale(t) # precompute self._mu_c return super().run(state, tlist, *args, **kwargs) + def start(self, state, t0, seed=None): + self._mu_c = {t0: 1} + return super().start(state, t0, seed=seed) + # Override "_restore_state" to include the martingale in the state def _restore_state(self, data, t, *, copy=True): # find state |psi> Date: Sat, 4 Feb 2023 12:07:41 +0900 Subject: [PATCH 041/602] Integrate changes suggested by @Ericgig, _check_completeness now uses only Qobj API --- qutip/solver/nm_mcsolve.py | 67 +++++++++++++------------------------- 1 file changed, 23 insertions(+), 44 deletions(-) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 69a5787e15..71131c2335 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -5,7 +5,7 @@ import scipy from .mcsolve import MCSolver from .result import McResult -from ..core import Qobj, QobjEvo, isket, ket2dm +from ..core import CoreOptions, QobjEvo, isket, ket2dm, qeye class NonMarkovianMCSolver(MCSolver): @@ -16,30 +16,19 @@ class NonMarkovianMCSolver(MCSolver): name = "nm_mcsolve" resultclass = McResult solver_options = { - "progress_bar": "text", - "progress_kwargs": {"chunk_size": 10}, - "store_final_state": False, - "store_states": None, - "keep_runs_results": False, - "method": "adams", - "map": "serial", - "job_timeout": None, - "num_cpus": None, - "bitgenerator": None, - "mc_corr_eps": 1e-10, - "norm_steps": 5, - "norm_t_tol": 1e-6, - "norm_tol": 1e-4, - "completeness_rtol": None, - "completeness_atol": None, + **MCSolver.solver_options, + "completeness_rtol": 1e-5, + "completeness_atol": 1e-8, } - # ops_and_rates is a list of tuples (L_i, Gamma_i), where Gamma_i = Gamma_i(t) is callable + # ops_and_rates is a list of tuples (L_i, Gamma_i), + # where Gamma_i = Gamma_i(t) is callable def __init__(self, H, ops_and_rates, *args, options=None, **kwargs): self.ops_and_rates = list(ops_and_rates) + self.options = options self._mu_c = None - self._a_parameter, L = self._check_completeness(ops_and_rates, options) + self._a_parameter, L = self._check_completeness(ops_and_rates) if L is not None: self.ops_and_rates.append((L, float)) # float() is zero @@ -50,29 +39,18 @@ def __init__(self, H, ops_and_rates, *args, options=None, **kwargs): # If not, creates an extra Lindblad operator so that it is # Returns: * the proportionality factor a # * the extra Lindblad operator (or None if not necessary) - @staticmethod - def _check_completeness(ops_and_rates, options=None): - tolerance_settings = {} - if options is not None: - rtol = options.get('completeness_rtol', None) - if rtol is not None: - tolerance_settings['rtol'] = rtol - - atol = options.get('completeness_atol', None) - if atol is not None: - tolerance_settings['atol'] = atol - - op = sum((f[0].dag() * f[0]).full() for f in ops_and_rates) - - a_candidate = op[0, 0] - if np.allclose(a_candidate * np.eye(len(op)), op, - **tolerance_settings): - return np.real(a_candidate), None - - w, _ = np.linalg.eig(op) - a = max(np.real(w)) - L = scipy.linalg.sqrtm(a * np.eye(len(op)) - op) # new Lindblad operator - return a, Qobj(L) + def _check_completeness(self, ops_and_rates): + op = sum((L.dag() * L) for L, _ in ops_and_rates) + + a_candidate = op.tr() / op.shape[0] + with CoreOptions(rtol=self.options["completeness_rtol"], + atol=self.options["completeness_atol"]): + if op == a_candidate * qeye(op.dims[0]): + return np.real(a_candidate), None + + a = max(op.eigenenergies()) + L = (a * qeye(op.dims[0]) - op).sqrtm() # new Lindblad operator + return a, L # Shifts all rate function by the function rate_shift # Returns c_i = L_i * sqrt(gamma_i) as QobjEvo objects @@ -120,7 +98,8 @@ def _discrete_martingale(self, collapses): for tk, ik in collapses] return np.prod(factors) - # Override "run" and "start" to initialize continuous part of martingale evolution + # Override "run" and "start" to initialize continuous part + # of martingale evolution def run(self, state, tlist, *args, **kwargs): self._mu_c = {tlist[0]: 1} for t in tlist[1:]: @@ -136,7 +115,7 @@ def _restore_state(self, data, t, *, copy=True): # find state |psi> Date: Tue, 7 Feb 2023 16:50:45 +0100 Subject: [PATCH 042/602] Set version 5.0.0a1 in changelog. --- doc/changelog.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 872b7d92e4..071f1e5de9 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -6,8 +6,8 @@ Change Log .. towncrier release notes start -Version 5.0.0 (February XX, 2023) -+++++++++++++++++++++++++++++++++ +Version 5.0.0a1 (February 7, 2023) +++++++++++++++++++++++++++++++++++ QuTiP 5 is a redesign of many of the core components of QuTiP (``Qobj``, ``QobjEvo``, solvers) to make them more consistent and more flexible. From 5856febc2b2c25ba2cd4b9e492397dbd4280b9f8 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 7 Feb 2023 14:55:27 -0500 Subject: [PATCH 043/602] Add pre release installation instruction --- README.md | 11 +++++++++++ doc/installation.rst | 22 ++++++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eb08599852..32e3a108ac 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,17 @@ All back releases are also available for download in the [releases section of th For the most complete set of release notes and changelogs for historic versions, see the [changelog](https://qutip.org/docs/latest/changelog.html) section in the documentation. +The pre release of QuTiP 5.0 is available on PyPI and can be installed from pip with: + +```bash +pip install --pre qutip +``` + +This version breaks compatibility with QuTiP 4.7 in many small ways. +Please see the [changelog](https://github.com/qutip/qutip/blob/master/doc/changelog.rst) for a list of changes, new features and deprecations. +This version should fully working, if you find any bugs, confusing documentation or missing features, please tell us on GitHub. + + Documentation ------------- diff --git a/doc/installation.rst b/doc/installation.rst index df3308a1fd..145277dca8 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -64,7 +64,7 @@ In addition, there are several optional packages that provide additional functio | ``pytest``, | 5.3+ | For running the test suite. | | ``pytest-rerunfailures`` | | | +--------------------------+--------------+-----------------------------------------------------+ -| LaTeX | TeXLive 2009+| Needed if using LaTeX in matplotlib figures, or for | +| LaTeX | TeXLive 2009+| Needed if using LaTeX in matplotlib figures, or for | | | | nice circuit drawings in IPython. | +--------------------------+--------------+-----------------------------------------------------+ @@ -128,6 +128,24 @@ You activate the new environment by running You can also install any more optional packages you want with ``conda install``, for example ``matplotlib``, ``ipython`` or ``jupyter``. + +Installation of the pre release of version 5 +============================================ + +QuTiP version 5 has been in development for some time and bring a lot of new features, reworking heavily the core functionalities of QuTiP. +It is available as a pre release on PyPI, anyone wanting to try the new features can install it with: + +.. code-block:: bash + + pip install --dev qutip + +We keep versions available as pre release fully working. +If you find any bugs, confusing documentation or missing features, please tell us on `github `_. + +This version breaks compatibility with QuTiP 4.7 in many small ways. +Please see the `changelog <./changelog.html>`_ for a list of changes, new features and deprecations. + + .. _install-from-source: Installing from Source @@ -192,7 +210,7 @@ To install OpenMP support, if available, run: This will attempt to load up OpenMP libraries during the compilation process, which depends on you having suitable C++ compiler and library support. If you are on Linux this is probably already done, but the compiler macOS ships with does not have OpenMP support. You will likely need to refer to external operating-system-specific guides for more detail here, as it may be very non-trivial to correctly configure. - + If you wish to contribute to the QuTiP project, then you will want to create your own fork of `the QuTiP git repository `_, clone this to a local folder, and install it into your Python environment using: .. code-block:: bash From 33db6b001f994f8b77cee4b4f13505fd224c4484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Tue, 7 Feb 2023 17:07:43 -0500 Subject: [PATCH 044/602] Apply suggestions from code review Co-authored-by: Simon Cross --- README.md | 4 ++-- doc/installation.rst | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 32e3a108ac..19a901e5da 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ All back releases are also available for download in the [releases section of th For the most complete set of release notes and changelogs for historic versions, see the [changelog](https://qutip.org/docs/latest/changelog.html) section in the documentation. -The pre release of QuTiP 5.0 is available on PyPI and can be installed from pip with: +The pre-release of QuTiP 5.0 is available on PyPI and can be installed using pip: ```bash pip install --pre qutip @@ -80,7 +80,7 @@ pip install --pre qutip This version breaks compatibility with QuTiP 4.7 in many small ways. Please see the [changelog](https://github.com/qutip/qutip/blob/master/doc/changelog.rst) for a list of changes, new features and deprecations. -This version should fully working, if you find any bugs, confusing documentation or missing features, please tell us on GitHub. +This version should be fully working. If you find any bugs, confusing documentation or missing features, please create a GitHub issue. Documentation diff --git a/doc/installation.rst b/doc/installation.rst index 145277dca8..efce6af430 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -129,21 +129,21 @@ You activate the new environment by running You can also install any more optional packages you want with ``conda install``, for example ``matplotlib``, ``ipython`` or ``jupyter``. -Installation of the pre release of version 5 +Installation of the pre-release of version 5 ============================================ -QuTiP version 5 has been in development for some time and bring a lot of new features, reworking heavily the core functionalities of QuTiP. -It is available as a pre release on PyPI, anyone wanting to try the new features can install it with: +QuTiP version 5 has been in development for some time and brings many new features, heavily reworks the core functionalities of QuTiP. +It is available as a pre-release on PyPI. Anyone wanting to try the new features can install it with: .. code-block:: bash - pip install --dev qutip + pip install --pre qutip -We keep versions available as pre release fully working. -If you find any bugs, confusing documentation or missing features, please tell us on `github `_. +We expect the pre-release to fully work. +If you find any bugs, confusing documentation or missing features, please tell create an issue on `github `_. This version breaks compatibility with QuTiP 4.7 in many small ways. -Please see the `changelog <./changelog.html>`_ for a list of changes, new features and deprecations. +Please see the :doc:`changelog` for a list of changes, new features and deprecations. .. _install-from-source: From fadbaf375883100da2155c73488b866d63b55ff5 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 7 Feb 2023 17:25:34 +0900 Subject: [PATCH 045/602] Reverted adding time parameter to _restore_state in all solvers --- qutip/solver/floquet.py | 5 ++--- qutip/solver/heom/bofin_solvers.py | 2 +- qutip/solver/mcsolve.py | 6 +++--- qutip/solver/multitraj.py | 8 ++++---- qutip/solver/nm_mcsolve.py | 14 +++++++++----- qutip/solver/solver_base.py | 11 +++++------ 6 files changed, 24 insertions(+), 22 deletions(-) diff --git a/qutip/solver/floquet.py b/qutip/solver/floquet.py index 80d0dc772e..7ce6cc4f11 100644 --- a/qutip/solver/floquet.py +++ b/qutip/solver/floquet.py @@ -915,15 +915,14 @@ def run(self, state0, tlist, *, floquet=False, args=None, e_ops=None): stats=stats, floquet_basis=self.floquet_basis, ) - results.add(tlist[0], - self._restore_state(_data0, tlist[0], copy=False)) + results.add(tlist[0], self._restore_state(_data0, copy=False)) stats["preparation time"] += time() - _time_start progress_bar = progess_bars[self.options["progress_bar"]]() progress_bar.start(len(tlist) - 1, **self.options["progress_kwargs"]) for t, state in self._integrator.run(tlist): progress_bar.update() - results.add(t, self._restore_state(state, t, copy=False)) + results.add(t, self._restore_state(state, copy=False)) progress_bar.finished() stats["run time"] = progress_bar.total_time() diff --git a/qutip/solver/heom/bofin_solvers.py b/qutip/solver/heom/bofin_solvers.py index 184937092e..99131ae990 100644 --- a/qutip/solver/heom/bofin_solvers.py +++ b/qutip/solver/heom/bofin_solvers.py @@ -1089,7 +1089,7 @@ def _prepare_state(self, state): return rho0_he - def _restore_state(self, state, t, *, copy=True): + def _restore_state(self, state, *, copy=True): n = self._sys_shape rho_shape = (n, n) rho_dims = self._sys_dims diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 97c31cdbb6..e79d7aa15a 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -394,7 +394,7 @@ def __init__(self, H, c_ops, *, options=None): super().__init__(rhs, options=options) - def _restore_state(self, data, t, *, copy=True): + def _restore_state(self, data, *, copy=True): """ Retore the Qobj state from its data. """ @@ -434,10 +434,10 @@ def _run_one_traj(self, seed, state, tlist, e_ops): result = Result(e_ops, {**self.options, "normalize_output": False}) generator = self._get_generator(seed) self._integrator.set_state(tlist[0], state, generator) - result.add(tlist[0], self._restore_state(state, tlist[0], copy=False)) + result.add(tlist[0], self._restore_state(state, copy=False)) for t in tlist[1:]: t, state = self._integrator.integrate(t, copy=False) - result.add(t, self._restore_state(state, t, copy=False)) + result.add(t, self._restore_state(state, copy=False)) result.collapse = self._integrator.collapses return seed, result diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index c2b2c65eb8..204178d116 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -100,8 +100,8 @@ def step(self, t, *, args=None, copy=True): if not self._integrator._is_set: raise RuntimeError("The `start` method must called first.") self._argument(args) - t, state = self._integrator.integrate(t, copy=False) - return self._restore_state(state, t, copy=copy) + _, state = self._integrator.integrate(t, copy=False) + return self._restore_state(state, copy=copy) def run(self, state, tlist, ntraj=1, *, args=None, e_ops=(), timeout=None, target_tol=None, seed=None): @@ -201,10 +201,10 @@ def _run_one_traj(self, seed, state, tlist, e_ops): result = Result(e_ops, self.options) generator = self._get_generator(seed) self._integrator.set_state(tlist[0], state, generator) - result.add(tlist[0], self._restore_state(state, tlist[0], copy=False)) + result.add(tlist[0], self._restore_state(state, copy=False)) for t in tlist[1:]: t, state = self._integrator.step(t, copy=False) - result.add(t, self._restore_state(state, t, copy=False)) + result.add(t, self._restore_state(state, copy=False)) return seed, result def _read_seed(self, seed, ntraj): diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 71131c2335..0ef84fdeef 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -98,6 +98,12 @@ def _discrete_martingale(self, collapses): for tk, ik in collapses] return np.prod(factors) + def _current_martingale(self): + t, *_ = self._integrator.get_state(copy=False) + collapses = self._integrator.collapses + return self._continuous_martingale(t) *\ + self._discrete_martingale(collapses) + # Override "run" and "start" to initialize continuous part # of martingale evolution def run(self, state, tlist, *args, **kwargs): @@ -111,17 +117,15 @@ def start(self, state, t0, seed=None): return super().start(state, t0, seed=seed) # Override "_restore_state" to include the martingale in the state - def _restore_state(self, data, t, *, copy=True): + def _restore_state(self, data, *, copy=True): # find state |psi> Date: Wed, 8 Feb 2023 13:28:12 +0900 Subject: [PATCH 046/602] Save average martingale values in custom result object for nm_mcsolve --- qutip/solver/nm_mcsolve.py | 45 +++++++++++++++++++++++++++----------- qutip/solver/result.py | 29 ++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 0ef84fdeef..b9afffc558 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -4,17 +4,23 @@ import numpy as np import scipy from .mcsolve import MCSolver -from .result import McResult +from .result import Result, NmmcResult from ..core import CoreOptions, QobjEvo, isket, ket2dm, qeye +def _2dm(q): + if isket(q): + return ket2dm(q) + return q + + class NonMarkovianMCSolver(MCSolver): r""" Monte-Carlo solver for master equation with negative rates. Based on the methods explained in arXiv:2209.08958 [quant-ph] """ name = "nm_mcsolve" - resultclass = McResult + resultclass = NmmcResult solver_options = { **MCSolver.solver_options, "completeness_rtol": 1e-5, @@ -116,16 +122,29 @@ def start(self, state, t0, seed=None): self._mu_c = {t0: 1} return super().start(state, t0, seed=seed) - # Override "_restore_state" to include the martingale in the state - def _restore_state(self, data, *, copy=True): - # find state |psi> Date: Wed, 8 Feb 2023 15:32:35 +0900 Subject: [PATCH 047/602] Made single trajectory result class generic in MultiTrajSolver to avoid code duplication in MCSolver and NonMarkovianMCSolver --- qutip/solver/mcsolve.py | 13 +++---------- qutip/solver/multitraj.py | 9 ++++----- qutip/solver/nm_mcsolve.py | 37 ++++++++----------------------------- qutip/solver/result.py | 23 ++++++++++++++++++++++- 4 files changed, 37 insertions(+), 45 deletions(-) diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index e79d7aa15a..48150c4555 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -1,12 +1,10 @@ __all__ = ['mcsolve', "MCSolver"] -import warnings - import numpy as np from ..core import QobjEvo, spre, spost, Qobj, unstack_columns from .multitraj import MultiTrajSolver from .solver_base import Solver, Integrator -from .result import McResult, Result +from .result import McResult, McTrajectory from .mesolve import mesolve, MESolver import qutip.core.data as _data from time import time @@ -349,6 +347,7 @@ class MCSolver(MultiTrajSolver): """ name = "mcsolve" resultclass = McResult + trajectoryclass = McTrajectory solver_options = { "progress_bar": "text", "progress_kwargs": {"chunk_size": 10}, @@ -431,13 +430,7 @@ def _run_one_traj(self, seed, state, tlist, e_ops): # multiprocessing, but will fail with multithreading. # If a thread base parallel map is created, eahc trajectory should use # a copy of the integrator. - result = Result(e_ops, {**self.options, "normalize_output": False}) - generator = self._get_generator(seed) - self._integrator.set_state(tlist[0], state, generator) - result.add(tlist[0], self._restore_state(state, copy=False)) - for t in tlist[1:]: - t, state = self._integrator.integrate(t, copy=False) - result.add(t, self._restore_state(state, copy=False)) + seed, result = super()._run_one_traj(seed, state, tlist, e_ops) result.collapse = self._integrator.collapses return seed, result diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 204178d116..08f49b1494 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -1,12 +1,10 @@ -from .. import Qobj, QobjEvo from .result import Result, MultiTrajResult from .parallel import _get_map from time import time from .solver_base import Solver import numpy as np -from copy import copy -__all__ = ["MultiTrajSolver", "TrajectorySolver"] +__all__ = ["MultiTrajSolver"] class MultiTrajSolver(Solver): @@ -30,6 +28,7 @@ class MultiTrajSolver(Solver): """ name = "generic multi trajectory" resultclass = MultiTrajResult + trajectoryclass = Result _avail_integrators = {} # Class of option used by the solver @@ -198,12 +197,12 @@ def _run_one_traj(self, seed, state, tlist, e_ops): """ Run one trajectory and return the result. """ - result = Result(e_ops, self.options) + result = self.trajectoryclass(e_ops, self.options) generator = self._get_generator(seed) self._integrator.set_state(tlist[0], state, generator) result.add(tlist[0], self._restore_state(state, copy=False)) for t in tlist[1:]: - t, state = self._integrator.step(t, copy=False) + t, state = self._integrator.integrate(t, copy=False) result.add(t, self._restore_state(state, copy=False)) return seed, result diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index b9afffc558..c0e7646f04 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -4,16 +4,10 @@ import numpy as np import scipy from .mcsolve import MCSolver -from .result import Result, NmmcResult +from .result import NmmcResult, NmmcTrajectory from ..core import CoreOptions, QobjEvo, isket, ket2dm, qeye -def _2dm(q): - if isket(q): - return ket2dm(q) - return q - - class NonMarkovianMCSolver(MCSolver): r""" Monte-Carlo solver for master equation with negative rates. @@ -21,6 +15,8 @@ class NonMarkovianMCSolver(MCSolver): """ name = "nm_mcsolve" resultclass = NmmcResult + # "solver" argument will be partially initialized in constructor + trajectoryclass = NmmcTrajectory solver_options = { **MCSolver.solver_options, "completeness_rtol": 1e-5, @@ -30,6 +26,8 @@ class NonMarkovianMCSolver(MCSolver): # ops_and_rates is a list of tuples (L_i, Gamma_i), # where Gamma_i = Gamma_i(t) is callable def __init__(self, H, ops_and_rates, *args, options=None, **kwargs): + self.trajectoryclass = partial(NmmcTrajectory, solver=self) + self.ops_and_rates = list(ops_and_rates) self.options = options self._mu_c = None @@ -126,25 +124,6 @@ def start(self, state, t0, seed=None): # Note that the returned state will be a density matrix with trace=mu def step(self, t, *, args=None, copy=True): state = super().step(t, args=args, copy=copy) - state_dm = _2dm(state) - return state_dm * self._current_martingale() - - # Override "_run_one_traj" to include and store the martingale - def _run_one_traj(self, seed, state, tlist, e_ops): - result = Result(e_ops, {**self.options, "normalize_output": False}) - generator = self._get_generator(seed) - self._integrator.set_state(tlist[0], state, generator) - - # martingale is one at the starting time - result.trace = [1] - state_dm = _2dm(self._restore_state(state, copy=False)) - result.add(tlist[0], state_dm) - - for t in tlist[1:]: - t, state = self._integrator.integrate(t, copy=False) - state_dm = _2dm(self._restore_state(state, copy=False)) - mu = self._current_martingale() - result.add(t, state_dm * mu) - result.trace.append(mu) - result.collapse = self._integrator.collapses - return seed, result + if isket(state): + state = ket2dm(state) + return state * self._current_martingale() diff --git a/qutip/solver/result.py b/qutip/solver/result.py index 087429230f..8d94bbdb52 100644 --- a/qutip/solver/result.py +++ b/qutip/solver/result.py @@ -1,6 +1,6 @@ """ Class for solve function results""" import numpy as np -from ..core import Qobj, QobjEvo, expect, qzero +from ..core import Qobj, QobjEvo, expect, isket, ket2dm, qzero __all__ = ["Result", "MultiTrajResult", "McResult"] @@ -866,6 +866,12 @@ def __add__(self, other): return new +class McTrajectory(Result): + def __init__(self, e_ops, options, *args, **kwargs): + super().__init__(e_ops, {**options, "normalize_output": False}, + *args, **kwargs) + + class McResult(MultiTrajResult): """ Base class for storing solver results. @@ -972,6 +978,21 @@ def runs_photocurrent(self): return measurements +class NmmcTrajectory(McTrajectory): + def __init__(self, e_ops, options, solver, *args, **kwargs): + super().__init__(e_ops, options, *args, **kwargs) + + self._solver = solver + self.trace = [] + + def add(self, t, state): + if isket(state): + state = ket2dm(state) + mu = self._solver._current_martingale() + super().add(t, state * mu) + self.trace.append(mu) + + class NmmcResult(McResult): def _add_trace(self, trajectory): new_trace = np.array(trajectory.trace) From fe801a87c73a745a6fd271cb8dd745f3c206a3bb Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Wed, 8 Feb 2023 12:44:50 +0100 Subject: [PATCH 048/602] Add readthedocs configuration. --- .readthedocs.yaml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000000..cfd48e0e2d --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,24 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +formats: + - epub + - pdf + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +python: + install: + - requirements: docs/requirements.txt + - method: pip + path: .[full] + +sphinx: + configuration: docs/conf.py + fail_on_warning: true \ No newline at end of file From 1fa3ec697208078608c9acf10c1cae19f693605d Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 8 Feb 2023 18:38:45 -0500 Subject: [PATCH 049/602] Add tests --- qutip/solver/sode/check.py | 145 +++------------- qutip/solver/sode/ito.py | 34 ++-- qutip/solver/sode/ssystem.pyx | 76 +++++++++ qutip/tests/solver/test_stochastic_system.py | 164 +++++++++++++++++++ 4 files changed, 277 insertions(+), 142 deletions(-) create mode 100644 qutip/tests/solver/test_stochastic_system.py diff --git a/qutip/solver/sode/check.py b/qutip/solver/sode/check.py index 5fc53a874b..39193220a8 100644 --- a/qutip/solver/sode/check.py +++ b/qutip/solver/sode/check.py @@ -3,123 +3,30 @@ from qutip.core import data as _data import qutip -def make_1_step(H, psi, sc_ops, solver, dt, dw=False): - ssolver = qutip.smesolve(H, psi, [0,dt], sc_ops=c_ops, _dry_run=1, solver=solver, nsubsteps=1) - state = qutip.operator_to_vector(ssolver.sso.state0).full().flatten() - func = getattr(ssolver, solver) - out = np.zeros_like(state) - if dw is False: - dw = np.array([1.] * 2 * len(sc_ops)) * dt**0.5 - func(0, dt, dw, state, out) - return qutip.Qobj( qutip.unstack_columns(out)) - - -def L0(system, f): - def _func(t, rho, dt=1e-6): - n = rho.shape[0] - f0 = f(t, rho) - - out = (f(t+dt, rho) - f0) / dt - - jac = np.zeros((n, n), dtype=complex) - for i in range(n): - dxi = qutip.basis(n, i).data - jac[:, i] = (f(t, rho + dt * dxi) - f0).to_array().flatten() / dt - out = out + qutip.data.Dense(jac) @ system.drift(t, rho) - - for i, j in product(range(n), repeat=2): - dxi = qutip.basis(n, i).data - dxj = qutip.basis(n, j).data - sec = f(t, (rho + dxi * dt + dxj * dt)) - sec = sec - f(t, (rho + dxj * dt)) - sec = sec - f(t, (rho + dxi * dt)) - sec = sec + f0 - sec = sec / dt / dt * 0.5 - for k in range(system.num_collapse): - out = out + ( - sec - * qutip.data.inner(dxi, system.diffusion(t, rho)[k]) - * qutip.data.inner(dxj, system.diffusion(t, rho)[k]) - ) - return out - return _func - - -def L(system, ii, f): - def _func(t, rho, dt=1e-6): - n = rho.shape[0] - jac = np.zeros((n, n), dtype=complex) - f0 = f(t, rho) - for i in range(n): - dxi = qutip.basis(n, i).data - jac[:, i] = (f(t, (rho + dt * dxi)) - f0).to_array().flatten() - return qutip.data.Dense(jac) @ system.diffusion(t, rho)[ii] / dt - return _func - - -def LL(system, ii, jj, f): - # Can be implemented as 2 calls of ``L``, but this use 2 ``dt`` which - # cannot be different. - def _func(t, rho, dt=1e-6): - f0 = f(t, rho) - bi = system.diffusion(t, rho)[ii] - bj = system.diffusion(t, rho)[jj] - out = rho *0. - n = rho.shape[0] - - for i, j in product(range(n), repeat=2): - dxi = qutip.basis(n, i, dtype="Dense").data - dxj = qutip.basis(n, j, dtype="Dense").data - sec = f(t, (rho + dxi * dt + dxj * dt)) - sec = sec - f(t, (rho + dxj * dt)) - sec = sec - f(t, (rho + dxi * dt)) - sec = sec + f0 - sec = sec / dt / dt - - out = out + ( - sec * qutip.data.inner(dxi, bi) * qutip.data.inner(dxj, bj) - ) - df = (f(t, (rho + dxj * dt)) - f0) / dt - db = (system.diffusion(t, (rho + dxi * dt))[jj] - system.diffusion(t, rho)[jj] ) / dt - - out = out + ( - df * qutip.data.inner(dxi, bi) * qutip.data.inner(dxj, db) - ) - - return out - return _func - - -def _check_equivalence(f, target, args): - dts = np.logspace(-5, -1, 9) - errors_dt = [ - _data.norm.l2(f(*args, dt=dt) - target) / dt - for dt in dts +def compute_step(system, method, state, ts, noise, **kw): + import qutip.solver.sode._sode as _sode + stepper = getattr(_sode, method)(system, **kw) + out = [] + for t in ts: + out.append(stepper.run(0, state.copy(), t, noise.dW(t), 1)) + return out + + +def get_error_order(system, method): + num_runs = 10 + ts = [ + 0.000001, 0.000002, 0.000005, 0.00001, 0.00002, 0.00005, + 0.0001, 0.0002, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, ] - poly = np.polyfit(dts, errors_dt, 1) - return -1 < poly[0] < 1 - - -def run_derr_check(solver, t, state): - N = solver.num_collapse - a = solver.drift - solver.set_state(t, state) - - assert solver.drift(t, state) == solver.a() - assert _check_equivalence(L0(solver, a), solver.L0a(), (t, state)) - - for i in range(N): - b = lambda *args: solver.diffusion(*args)[i] - assert b(t, state) == solver.bi(i) - assert _check_equivalence(L0(solver, b), solver.L0bi(i), (t, state)) - assert _check_equivalence(L(solver, i, a), solver.Lia(i), (t, state)) - - for j in range(N): - assert _check_equivalence( - L(solver, j, b), solver.Libj(j, i), (t, state) - ) - - for k in range(N): - assert _check_equivalence( - LL(solver, k, j, b), solver.LiLjbk(k, j, i), (t, state) - ) + state = rand_ket(system.dims[0]).data + err = np.zeros(len(ts), dtype=float) + for _ in range(num_runs): + noise = MultiNoise(0.1, 0.000001) + out = compute_step(system, method, state, ts, noise) + + for i, (o, t) in enumerate(zip(out, ts)): + got = o.to_array()[0,0] + target = system.analytic(t, noise.dw(t)[0,0]) + err[i] += (np.abs(got - target)) + err /= num_runs + return np.polyfit(np.log(ts), np.log(err), 1) diff --git a/qutip/solver/sode/ito.py b/qutip/solver/sode/ito.py index 294e517a36..d3ea5df255 100644 --- a/qutip/solver/sode/ito.py +++ b/qutip/solver/sode/ito.py @@ -1,21 +1,5 @@ import numpy as np -class ItoNoise: - def __init__(self, T, dt): - N = int(np.round(T / dt)) - self.T = T - self.dt = dt - self.noise = np.random.randn(N) * dt**0.5 - - def dw(self, dt): - # I(j) - N = int(np.round(dt /self.dt)) - return self.noise.reshape(-1, N).sum(axis=1) - - def dz(self, dt): - # I(0, j) - N = int(np.round(dt /self.dt)) - return self.noise.reshape(-1, N) @ np.arange(N-0.5, 0, -1) * self.dt class MultiNoise: def __init__(self, T, dt, num=1): @@ -32,17 +16,21 @@ def dw(self, dt): def dz(self, dt): N = int(np.round(dt /self.dt)) return np.einsum( - "ijk,j", + "ijk,j->ik", self.noise.reshape(-1, N, self.num), np.arange(N-0.5, 0, -1) ) * self.dt def dW(self, dt): N = int(np.round(dt / self.dt)) - noise = self.noise + noise = self.noise.copy() if noise.shape[0] % N: - noise = noise[:-noise.shape[0] % N] - out = np.empty((self.num, 2), dtype=float) - out[:, 0] = noise[:N, :].sum(axis=0) - out[:, 1] = np.arange(N-0.5, 0, -1) @ noise[:N, :] * self.dt - return out.T + noise = noise[:-(noise.shape[0] % N)] + out = np.empty((noise.shape[0] // N, self.num, 2), dtype=float) + out[:, :, 0] = noise.reshape(-1, N, self.num).sum(axis=1) + out[:, :, 1] = np.einsum( + "ijk,j->ik", + self.noise.reshape(-1, N, self.num), + np.arange(N-0.5, 0, -1) + ) * self.dt + return out diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index 626d80090d..8fb42a68f9 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -402,3 +402,79 @@ cdef class StochasticOpenSystem(_StochasticSystem): _data.imul_dense(self._L0a, 1/self.dt) self.L.matmul_data(self.t, self._a, self._L0a) self._L0a_set = True + + +cdef class SimpleStochasticSystem(_StochasticSystem): + """ + Simple system that can be solver analytically. + """ + cdef QobjEvo H + cdef list c_ops + cdef float dt + + def __init__(self, H, c_ops): + self.H = -1j * H + self.c_ops = c_ops + + self.num_collapse = len(self.c_ops) + self.issuper = False + self.dims = self.H.dims + self.dt = 1e-6 + + cpdef Data drift(self, t, Data state): + return self.H.matmul_data(t, state) + + cpdef list diffusion(self, t, Data state): + cdef int i + cdef out = [] + for i in range(self.num_collapse): + out.append(self.c_ops[i].matmul_data(t, state)) + return out + + cpdef void set_state(self, double t, Data state): + self.t = t + self.state = _data.to(_data.Dense, state) + + cpdef Data a(self): + return self.H.matmul_data(self.t, self.state) + + cpdef Data bi(self, int i): + return self.c_ops[i].matmul_data(self.t, self.state) + + cpdef Data Libj(self, int i, int j): + bj = self.c_ops[i].matmul_data(self.t, self.state) + return self.c_ops[j].matmul_data(self.t, bj) + + cpdef Data Lia(self, int i): + bi = self.c_ops[i].matmul_data(self.t, self.state) + return self.H.matmul_data(self.t, bi) + + cpdef Data L0bi(self, int i): + # L0bi = abi' + dbi/dt + Sum_j bjbjbi"/2 + a = self.H.matmul_data(self.t, self.state) + abi = self.c_ops[i].matmul_data(self.t, a) + b = self.c_ops[i].matmul_data(self.t, self.state) + bdt = self.c_ops[i].matmul_data(self.t + self.dt, self.state) + return abi + (bdt - b) / self.dt + + cpdef Data LiLjbk(self, int i, int j, int k): + bk = self.c_ops[k].matmul_data(self.t, self.state) + Ljbk = self.c_ops[j].matmul_data(self.t, bk) + return self.c_ops[i].matmul_data(self.t, Ljbk) + + cpdef Data L0a(self): + # L0a = a'a + da/dt + bba"/2 (a" = 0) + a = self.H.matmul_data(self.t, self.state) + aa = self.H.matmul_data(self.t, a) + adt = self.H.matmul_data(self.t + self.dt, self.state) + return aa + (adt - a) / self.dt + + def analytic(self, t, W): + """ + Analytic solution, H and all c_ops must commute. + """ + out = self.H.full() * t + for i in range(self.num_collapse): + out += self.c_ops[i].full() * W[i] + out -= 0.5 * self.c_ops[i].full() @ self.c_ops[i].full() * t + return np.exp(out) diff --git a/qutip/tests/solver/test_stochastic_system.py b/qutip/tests/solver/test_stochastic_system.py new file mode 100644 index 0000000000..4925ec043f --- /dev/null +++ b/qutip/tests/solver/test_stochastic_system.py @@ -0,0 +1,164 @@ +import numpy as np +from qutip import qeye, num, destroy, create, QobjEvo, basis, rand_herm +from qutip.solver.sode.ssystem import * +import qutip.data as _data +import pytest + + +def L0(system, f): + def _func(t, rho, dt=1e-6): + n = rho.shape[0] + f0 = f(t, rho) + + out = (f(t+dt, rho) - f0) / dt + + jac = np.zeros((n, n), dtype=complex) + for i in range(n): + dxi = basis(n, i).data + jac[:, i] = (f(t, rho + dt * dxi) - f0).to_array().flatten() / dt + out = out + _data.Dense(jac) @ system.drift(t, rho) + + for i, j in product(range(n), repeat=2): + dxi = basis(n, i).data + dxj = basis(n, j).data + sec = f(t, (rho + dxi * dt + dxj * dt)) + sec = sec - f(t, (rho + dxj * dt)) + sec = sec - f(t, (rho + dxi * dt)) + sec = sec + f0 + sec = sec / dt / dt * 0.5 + for k in range(system.num_collapse): + out = out + ( + sec + * _data.inner(dxi, system.diffusion(t, rho)[k]) + * _data.inner(dxj, system.diffusion(t, rho)[k]) + ) + return out + return _func + + +def L(system, ii, f): + def _func(t, rho, dt=1e-6): + n = rho.shape[0] + jac = np.zeros((n, n), dtype=complex) + f0 = f(t, rho) + for i in range(n): + dxi = basis(n, i).data + jac[:, i] = (f(t, (rho + dt * dxi)) - f0).to_array().flatten() + return _data.Dense(jac) @ system.diffusion(t, rho)[ii] / dt + return _func + + +def LL(system, ii, jj, f): + # Can be implemented as 2 calls of ``L``, but would use 2 ``dt`` which + # cannot be different. + def _func(t, rho, dt=1e-6): + f0 = f(t, rho) + bi = system.diffusion(t, rho)[ii] + bj = system.diffusion(t, rho)[jj] + out = rho *0. + n = rho.shape[0] + + for i, j in product(range(n), repeat=2): + dxi = basis(n, i, dtype="Dense").data + dxj = basis(n, j, dtype="Dense").data + sec = f(t, (rho + dxi * dt + dxj * dt)) + sec = sec - f(t, (rho + dxj * dt)) + sec = sec - f(t, (rho + dxi * dt)) + sec = sec + f0 + sec = sec / dt / dt + + out = out + ( + sec * _data.inner(dxi, bi) * _data.inner(dxj, bj) + ) + df = (f(t, (rho + dxj * dt)) - f0) / dt + db = ( + system.diffusion(t, (rho + dxi * dt))[jj] + - system.diffusion(t, rho)[jj] + ) / dt + + out = out + ( + df * _data.inner(dxi, bi) * _data.inner(dxj, db) + ) + + return out + return _func + + +def _check_equivalence(f, target, args): + """ + Check that the error is proportional to `dt`. + """ + dts = np.logspace(-5, -1, 9) + errors_dt = [ + _data.norm.l2(f(*args, dt=dt) - target) / dt + for dt in dts + ] + poly = np.polyfit(dts, errors_dt, 1) + return -1 < poly[0] < 1 + + +def _run_derr_check(solver, state): + """ + + """ + t = 0 + N = solver.num_collapse + a = solver.drift + solver.set_state(t, state) + + assert solver.drift(t, state) == solver.a() + assert _check_equivalence(L0(solver, a), solver.L0a(), (t, state)) + + for i in range(N): + b = lambda *args: solver.diffusion(*args)[i] + assert b(t, state) == solver.bi(i) + assert _check_equivalence(L0(solver, b), solver.L0bi(i), (t, state)) + assert _check_equivalence(L(solver, i, a), solver.Lia(i), (t, state)) + + for j in range(N): + assert _check_equivalence( + L(solver, j, b), solver.Libj(j, i), (t, state) + ) + + for k in range(N): + assert _check_equivalence( + LL(solver, k, j, b), solver.LiLjbk(k, j, i), (t, state) + ) + + +def _make_oper(kind, N): + if kind == "qeye": + out = qeye(N) + elif kind == "destroy": + out = destroy(N) + elif kind == "destroy2": + out = destroy(N)**2 + elif kind == "tridiag": + out = destroy(N) + num(N) + create(N) + elif kind == "td": + out = [num(N), [destroy(N) + create(N), lambda t: t]] + elif kind == "rand": + out = rand_herm(N) + return QobjEvo(out) + + +@pytest.mark.parametrize(['H', 'c_ops'], [ + pytest.param("qeye", ["destroy"], id='simple'), + pytest.param("tridiag", ["destroy"], id='simple'), + pytest.param("qeye", ["destroy", "destroy2"], id='2 c_ops'), + pytest.param("td", ["destroy"], id='H td'), + pytest.param("qeye", ["td"], id='c_ops td'), + pytest.param("rand", ["rand"], id='random'), +]) +@pytest.mark.parametrize('type', ["sse", "sme"]) +@pytest.mark.parametrize('heterodyne', [False, True]) +def test_system(H, c_ops, type, heterodyne): + N = 5 + H = _make_oper(H, N) + c_ops = [_make_oper(op, N) for op in c_ops] + if type == "sse": + system = StochasticClosedSystem(H, c_ops, heterodyne) + else: + system = StochasticOpenSystem(H, c_ops, heterodyne) + state = basis(N, N-2).data + _run_derr_check(system, state) From 3d5761a7d43728e6774c4c42b04f38b5e23d1341 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 10 Feb 2023 11:57:35 +0100 Subject: [PATCH 050/602] Remove ignore of pyximport warnings in coefficient. --- qutip/core/coefficient.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/qutip/core/coefficient.py b/qutip/core/coefficient.py index 545ce271ff..1c9b0e4e38 100644 --- a/qutip/core/coefficient.py +++ b/qutip/core/coefficient.py @@ -32,17 +32,6 @@ "clean_compiled_coefficient"] -@contextmanager -def _ignore_import_warning_for_pyximporter(): - """ - A helper for ignoring PyxImporter import warnings generated by Python 3.10+ - because PyxImporter has no .find_spec method. - """ - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=ImportWarning) - yield - - class StringParsingWarning(Warning): pass @@ -358,8 +347,7 @@ def try_import(file_name, parsed_in): name collision. """ try: - with _ignore_import_warning_for_pyximporter(): - mod = importlib.import_module(file_name) + mod = importlib.import_module(file_name) except ModuleNotFoundError: # Coefficient does not exist, to compile as file_name return None @@ -499,9 +487,8 @@ def compile_code(code, file_name, parsed, c_opt): include_dirs=[np.get_include()], language='c++' ) - with _ignore_import_warning_for_pyximporter(): - ext_modules = cythonize(coeff_file, force=True) - setup(ext_modules=ext_modules) + ext_modules = cythonize(coeff_file, force=True) + setup(ext_modules=ext_modules) except Exception as e: if c_opt['clean_on_error']: for file in glob.glob(file_name + "*"): From d38c1b6d16af145ba3935a200f2f9648a6526ed9 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 10 Feb 2023 12:00:14 +0100 Subject: [PATCH 051/602] Re-enable ImportWarning test errors. --- .github/workflows/tests.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b6c5261e85..cc89348ab3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -69,9 +69,6 @@ jobs: # Python 3.10 and numpy 1.22 # Use conda-forge to provide numpy 1.22 - # Ignore ImportWarning because pyximport registered an importer - # PyxImporter that does not have a find_spec method and this raises - # a warning on Python 3.10 # Ignore DeprecationWarnings raised by cvxpy importing scipy.sparse.X # under SciPy 1.8.0+. # Ignore DeprecationWarnings raised by versions of @@ -82,13 +79,10 @@ jobs: os: ubuntu-latest python-version: "3.10" condaforge: 1 - pytest-extra-options: "-W ignore::ImportWarning -W ignore::DeprecationWarning:cvxpy.interface.scipy_wrapper -W ignore:Absolute:DeprecationWarning" + pytest-extra-options: "-W ignore::DeprecationWarning:cvxpy.interface.scipy_wrapper -W ignore:Absolute:DeprecationWarning" # Python 3.11 and latest numpy # Use conda-forge to provide Python 3.11 and latest numpy - # Ignore ImportWarning because pyximport registered an importer - # PyxImporter that does not have a find_spec method and this raises - # a warning on Python 3.10 # Ignore DeprecationWarnings raised by cvxpy importing scipy.sparse.X # under SciPy 1.8.0+. # Ignore DeprecationWarnings raised by versions of @@ -100,7 +94,7 @@ jobs: python-version: "3.11" condaforge: 1 conda-extra-pkgs: "suitesparse" # for compiling cvxopt - pytest-extra-options: "-W ignore::ImportWarning -W ignore::DeprecationWarning:cvxpy.interface.scipy_wrapper -W ignore:Absolute:DeprecationWarning -W ignore::DeprecationWarning:Cython.Tempita" + pytest-extra-options: "-W ignore::DeprecationWarning:cvxpy.interface.scipy_wrapper -W ignore:Absolute:DeprecationWarning -W ignore::DeprecationWarning:Cython.Tempita" # Windows. Once all tests pass without special options needed, this # can be moved to the main os list in the test matrix. All the tests @@ -111,7 +105,7 @@ jobs: - case-name: Windows Latest os: windows-latest python-version: "3.10" - pytest-extra-options: "-W ignore::ImportWarning -k 'not (test_correlation or test_interpolate or test_mcsolve)'" + pytest-extra-options: "-k 'not (test_correlation or test_interpolate or test_mcsolve)'" steps: - uses: actions/checkout@v3 From e955c75e8498ac4e8c5efbace6996b9646d1358f Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 10 Feb 2023 14:36:43 +0100 Subject: [PATCH 052/602] Remove disabling of cvxpy warnings. These were fixed in cvxpy 1.3. --- .github/workflows/tests.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cc89348ab3..1a9fc7feb5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -69,8 +69,6 @@ jobs: # Python 3.10 and numpy 1.22 # Use conda-forge to provide numpy 1.22 - # Ignore DeprecationWarnings raised by cvxpy importing scipy.sparse.X - # under SciPy 1.8.0+. # Ignore DeprecationWarnings raised by versions of # setuptools >= 65.0.0 during pyximport imports This can be removed # once https://github.com/cython/cython/issues/4985 @@ -79,12 +77,10 @@ jobs: os: ubuntu-latest python-version: "3.10" condaforge: 1 - pytest-extra-options: "-W ignore::DeprecationWarning:cvxpy.interface.scipy_wrapper -W ignore:Absolute:DeprecationWarning" + pytest-extra-options: "-W ignore:Absolute:DeprecationWarning" # Python 3.11 and latest numpy # Use conda-forge to provide Python 3.11 and latest numpy - # Ignore DeprecationWarnings raised by cvxpy importing scipy.sparse.X - # under SciPy 1.8.0+. # Ignore DeprecationWarnings raised by versions of # setuptools >= 65.0.0 during pyximport imports This can be removed # once https://github.com/cython/cython/issues/4985 @@ -94,7 +90,7 @@ jobs: python-version: "3.11" condaforge: 1 conda-extra-pkgs: "suitesparse" # for compiling cvxopt - pytest-extra-options: "-W ignore::DeprecationWarning:cvxpy.interface.scipy_wrapper -W ignore:Absolute:DeprecationWarning -W ignore::DeprecationWarning:Cython.Tempita" + pytest-extra-options: "-W ignore:Absolute:DeprecationWarning -W ignore::DeprecationWarning:Cython.Tempita" # Windows. Once all tests pass without special options needed, this # can be moved to the main os list in the test matrix. All the tests From b4e5756076706fa1da6204f9a37c1b9da43cac6d Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 10 Feb 2023 14:38:34 +0100 Subject: [PATCH 053/602] Remove disabling of pyximport absolute path warnings (QuTiP 5 no longer uses pyximport). --- .github/workflows/tests.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1a9fc7feb5..c5428dfa2c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -69,28 +69,18 @@ jobs: # Python 3.10 and numpy 1.22 # Use conda-forge to provide numpy 1.22 - # Ignore DeprecationWarnings raised by versions of - # setuptools >= 65.0.0 during pyximport imports This can be removed - # once https://github.com/cython/cython/issues/4985 - # is fixed and released. - case-name: Python 3.10 os: ubuntu-latest python-version: "3.10" condaforge: 1 - pytest-extra-options: "-W ignore:Absolute:DeprecationWarning" # Python 3.11 and latest numpy # Use conda-forge to provide Python 3.11 and latest numpy - # Ignore DeprecationWarnings raised by versions of - # setuptools >= 65.0.0 during pyximport imports This can be removed - # once https://github.com/cython/cython/issues/4985 - # is fixed and released. - case-name: Python 3.11 os: ubuntu-latest python-version: "3.11" condaforge: 1 conda-extra-pkgs: "suitesparse" # for compiling cvxopt - pytest-extra-options: "-W ignore:Absolute:DeprecationWarning -W ignore::DeprecationWarning:Cython.Tempita" # Windows. Once all tests pass without special options needed, this # can be moved to the main os list in the test matrix. All the tests From 19f5bc8c3be0a134188ae02528a1b204d491da47 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 10 Feb 2023 16:39:37 +0100 Subject: [PATCH 054/602] Re-enable ignoring the cgi deprecation raised by Cython.Tempita. --- .github/workflows/tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c5428dfa2c..ca207a7b95 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -76,11 +76,16 @@ jobs: # Python 3.11 and latest numpy # Use conda-forge to provide Python 3.11 and latest numpy + # Ignore deprecation of the cgi module in Python 3.11 that is + # still imported by Cython.Tempita. This was addressed in + # https://github.com/cython/cython/pull/5128 but not backported + # to any currently released version. - case-name: Python 3.11 os: ubuntu-latest python-version: "3.11" condaforge: 1 conda-extra-pkgs: "suitesparse" # for compiling cvxopt + pytest-extra-options: "-W ignore::DeprecationWarning:Cython.Tempita" # Windows. Once all tests pass without special options needed, this # can be moved to the main os list in the test matrix. All the tests From 882fa8b900c3f1d1b279ef1a0333f26a0b3f308e Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 10 Feb 2023 14:16:48 -0500 Subject: [PATCH 055/602] remove shift_dt --- qutip/core/cy/qobjevo.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index ec4983c7cd..daf2f2942f 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -294,7 +294,7 @@ cdef class QobjEvo: out.shape = shape out.type = type out.superrep = superrep - out._issuper, out._isoper, out._shift_dt = flags + out._issuper, out._isoper = flags return out def _getstate(self): @@ -302,14 +302,14 @@ cdef class QobjEvo: # For jax pytree representation # auto_pickle create similar method __getstate__, but since it's # automatically created, it could change depending on cython version - # etc. so we create our own. + # etc., so we create our own. return { "elements": self.elements, "dims": self.dims, "shape": self.shape, "type": self.type, "superrep": self.superrep, - "flags": (self._issuper, self._isoper, self._shift_dt) + "flags": (self._issuper, self._isoper,) } def __call__(self, double t, dict _args=None, **kwargs): From 2fa0917407df8c27ad06a1fe6f07941ed44e922a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 10 Feb 2023 16:23:59 -0500 Subject: [PATCH 056/602] Simplify default dtype --- qutip/core/coefficient.py | 4 ++ qutip/core/data/convert.pyx | 13 ------- qutip/core/operators.py | 69 +++++++++++++++++++-------------- qutip/core/states.py | 76 ++++++++++++++++++++++--------------- qutip/partial_transpose.py | 4 +- qutip/random_objects.py | 28 +++++++++----- 6 files changed, 111 insertions(+), 83 deletions(-) diff --git a/qutip/core/coefficient.py b/qutip/core/coefficient.py index 1119643a67..9aafca4dd3 100644 --- a/qutip/core/coefficient.py +++ b/qutip/core/coefficient.py @@ -51,6 +51,10 @@ def _return(base, **kwargs): return base +# The `coefficient` function is dispatcher for the type of the `base` to the +# function that create the `Coefficient` object. `coefficient_builders` store +# the map `type -> function(base, **kw)`. Optional module can add their +# `Coefficient` specialization here. coefficient_builders = { Coefficient: _return, np.ndarray: InterCoefficient, diff --git a/qutip/core/data/convert.pyx b/qutip/core/data/convert.pyx index a2419fc682..c2feed3dd1 100644 --- a/qutip/core/data/convert.pyx +++ b/qutip/core/data/convert.pyx @@ -18,7 +18,6 @@ import numbers import numpy as np from scipy.sparse import dok_matrix, csgraph -from qutip.settings import settings cimport cython __all__ = ['to', 'create'] @@ -90,16 +89,6 @@ cdef class _partial_converter: return "" -cdef class _default_dtype: - cdef type dtype - - def __init__(self, dtype): - self.dtype = dtype - - def __call__(self): - return settings.core["default_dtype"] or self.dtype - - # While `_to` and `_create` are defined as objects here, they are actually # exported by `data.__init__.py` as singleton function objects of their # respective types (without the leading underscore). @@ -319,8 +308,6 @@ cdef class _to: registered, or if ``dtype`` is a type, but not a known data-layer type. """ - if type(dtype) is _default_dtype: - dtype = dtype() if type(dtype) is type: if dtype not in self.dtypes: raise ValueError( diff --git a/qutip/core/operators.py b/qutip/core/operators.py index cfc1c3ce9e..f2921882a9 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -18,11 +18,11 @@ from . import data as _data from .qobj import Qobj from .dimensions import flatten -from .data.convert import _default_dtype +from .. import settings def qdiags(diagonals, offsets=None, dims=None, shape=None, *, - dtype=_default_dtype(_data.CSR)): + dtype=None): """ Constructs an operator from an array of diagonals. @@ -60,12 +60,13 @@ def qdiags(diagonals, offsets=None, dims=None, shape=None, *, [ 0. 0. 0. 0. ]] """ + dtype = dtype or settings.core["default_dtype"] or _data.CSR offsets = [0] if offsets is None else offsets data = _data.diag[dtype](diagonals, offsets, shape) return Qobj(data, dims=dims, type='oper', copy=False) -def jmat(j, which=None, *, dtype=_default_dtype(_data.CSR)): +def jmat(j, which=None, *, dtype=None): """Higher-order spin operators: Parameters @@ -109,12 +110,12 @@ def jmat(j, which=None, *, dtype=_default_dtype(_data.CSR)): [ 0. 0. 0.] [ 0. 0. -1.]]] - Notes ----- If no 'args' input, then returns array of ['x','y','z'] operators. """ + dtype = dtype or settings.core["default_dtype"] or _data.CSR if int(2 * j) != 2 * j or j < 0: raise ValueError('j must be a non-negative integer or half-integer') @@ -146,7 +147,7 @@ def jmat(j, which=None, *, dtype=_default_dtype(_data.CSR)): raise ValueError('Invalid spin operator: ' + which) -def _jplus(j, *, dtype=_default_dtype(_data.CSR)): +def _jplus(j, *, dtype=None): """ Internal functions for generating the data representing the J-plus operator. @@ -156,10 +157,11 @@ def _jplus(j, *, dtype=_default_dtype(_data.CSR)): return _data.diag[dtype](data, 1) -def _jz(j, *, dtype=_default_dtype(_data.CSR)): +def _jz(j, *, dtype=None): """ Internal functions for generating the data representing the J-z operator. """ + dtype = dtype or settings.core["default_dtype"] or _data.CSR N = int(2*j + 1) data = np.array([j-k for k in range(N)], dtype=complex) return _data.diag[dtype](data, 0) @@ -168,7 +170,7 @@ def _jz(j, *, dtype=_default_dtype(_data.CSR)): # # Spin j operators: # -def spin_Jx(j, *, dtype=_default_dtype(_data.CSR)): +def spin_Jx(j, *, dtype=None): """Spin-j x operator Parameters @@ -189,7 +191,7 @@ def spin_Jx(j, *, dtype=_default_dtype(_data.CSR)): return jmat(j, 'x', dtype=dtype) -def spin_Jy(j, *, dtype=_default_dtype(_data.CSR)): +def spin_Jy(j, *, dtype=None): """Spin-j y operator Parameters @@ -210,7 +212,7 @@ def spin_Jy(j, *, dtype=_default_dtype(_data.CSR)): return jmat(j, 'y', dtype=dtype) -def spin_Jz(j, *, dtype=_default_dtype(_data.CSR)): +def spin_Jz(j, *, dtype=None): """Spin-j z operator Parameters @@ -231,7 +233,7 @@ def spin_Jz(j, *, dtype=_default_dtype(_data.CSR)): return jmat(j, 'z', dtype=dtype) -def spin_Jm(j, *, dtype=_default_dtype(_data.CSR)): +def spin_Jm(j, *, dtype=None): """Spin-j annihilation operator Parameters @@ -252,7 +254,7 @@ def spin_Jm(j, *, dtype=_default_dtype(_data.CSR)): return jmat(j, '-', dtype=dtype) -def spin_Jp(j, *, dtype=_default_dtype(_data.CSR)): +def spin_Jp(j, *, dtype=None): """Spin-j creation operator Parameters @@ -273,7 +275,7 @@ def spin_Jp(j, *, dtype=_default_dtype(_data.CSR)): return jmat(j, '+', dtype=dtype) -def spin_J_set(j, *, dtype=_default_dtype(_data.CSR)): +def spin_J_set(j, *, dtype=None): """Set of spin-j operators (x, y, z) Parameters @@ -386,7 +388,7 @@ def sigmaz(): return _SIGMAZ.copy() -def destroy(N, offset=0, *, dtype=_default_dtype(_data.CSR)): +def destroy(N, offset=0, *, dtype=None): """ Destruction (lowering) operator. @@ -418,13 +420,14 @@ def destroy(N, offset=0, *, dtype=_default_dtype(_data.CSR)): [ 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j 1.73205081+0.j] [ 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j]] """ + dtype = dtype or settings.core["default_dtype"] or _data.CSR if not isinstance(N, (int, np.integer)): # raise error if N not integer raise ValueError("Hilbert space dimension must be integer value") data = np.sqrt(np.arange(offset+1, N+offset, dtype=complex)) return qdiags(data, 1, dtype=dtype) -def create(N, offset=0, *, dtype=_default_dtype(_data.CSR)): +def create(N, offset=0, *, dtype=None): """ Creation (raising) operator. @@ -460,6 +463,7 @@ def create(N, offset=0, *, dtype=_default_dtype(_data.CSR)): [ 0.00000000+0.j 1.41421356+0.j 0.00000000+0.j 0.00000000+0.j] [ 0.00000000+0.j 0.00000000+0.j 1.73205081+0.j 0.00000000+0.j]] """ + dtype = dtype or settings.core["default_dtype"] or _data.CSR if not isinstance(N, (int, np.integer)): # raise error if N not integer raise ValueError("Hilbert space dimension must be integer value") data = np.sqrt(np.arange(offset+1, N+offset, dtype=complex)) @@ -495,7 +499,7 @@ def _implicit_tensor_dimensions(dimensions): return np.prod(flat), [dimensions, dimensions] -def qzero(dimensions, *, dtype=_default_dtype(_data.CSR)): +def qzero(dimensions, *, dtype=None): """ Zero operator. @@ -517,6 +521,7 @@ def qzero(dimensions, *, dtype=_default_dtype(_data.CSR)): Zero operator Qobj. """ + dtype = dtype or settings.core["default_dtype"] or _data.CSR size, dimensions = _implicit_tensor_dimensions(dimensions) # A sparse matrix with no data is equal to a zero matrix. type_ = 'super' if isinstance(dimensions[0][0], list) else 'oper' @@ -524,7 +529,7 @@ def qzero(dimensions, *, dtype=_default_dtype(_data.CSR)): isherm=True, isunitary=False, copy=False) -def qeye(dimensions, *, dtype=_default_dtype(_data.CSR)): +def qeye(dimensions, *, dtype=None): """ Identity operator. @@ -564,6 +569,7 @@ def qeye(dimensions, *, dtype=_default_dtype(_data.CSR)): [0. 0. 0. 1.]] """ + dtype = dtype or settings.core["default_dtype"] or _data.CSR size, dimensions = _implicit_tensor_dimensions(dimensions) type_ = 'super' if isinstance(dimensions[0][0], list) else 'oper' return Qobj(_data.identity[dtype](size), dims=dimensions, type=type_, @@ -574,7 +580,7 @@ def qeye(dimensions, *, dtype=_default_dtype(_data.CSR)): identity = qeye -def position(N, offset=0, *, dtype=_default_dtype(_data.CSR)): +def position(N, offset=0, *, dtype=None): """ Position operator x=1/sqrt(2)*(a+a.dag()) @@ -602,7 +608,7 @@ def position(N, offset=0, *, dtype=_default_dtype(_data.CSR)): return position -def momentum(N, offset=0, *, dtype=_default_dtype(_data.CSR)): +def momentum(N, offset=0, *, dtype=None): """ Momentum operator p=-1j/sqrt(2)*(a-a.dag()) @@ -630,7 +636,7 @@ def momentum(N, offset=0, *, dtype=_default_dtype(_data.CSR)): return momentum -def num(N, offset=0, *, dtype=_default_dtype(_data.CSR)): +def num(N, offset=0, *, dtype=None): """ Quantum object for number operator. @@ -662,11 +668,12 @@ def num(N, offset=0, *, dtype=_default_dtype(_data.CSR)): [0 0 2 0] [0 0 0 3]] """ + dtype = dtype or settings.core["default_dtype"] or _data.CSR data = np.arange(offset, offset + N, dtype=complex) return qdiags(data, 0, dtype=dtype) -def squeeze(N, z, offset=0, *, dtype=_default_dtype(_data.CSR)): +def squeeze(N, z, offset=0, *, dtype=None): """Single-mode squeezing operator. Parameters @@ -703,7 +710,7 @@ def squeeze(N, z, offset=0, *, dtype=_default_dtype(_data.CSR)): [ 0.00000000+0.j -0.30142443+0.j 0.00000000+0.j 0.95349007+0.j]] """ - asq = destroy(N, offset=offset) ** 2 + asq = destroy(N, offset=offset, dtype=dtype) ** 2 op = 0.5*np.conj(z)*asq - 0.5*z*asq.dag() return op.expm(dtype=dtype) @@ -737,7 +744,7 @@ def squeezing(a1, a2, z): return b.expm() -def displace(N, alpha, offset=0, *, dtype=_default_dtype(_data.Dense)): +def displace(N, alpha, offset=0, *, dtype=None): """Single-mode displacement operator. Parameters @@ -773,6 +780,7 @@ def displace(N, alpha, offset=0, *, dtype=_default_dtype(_data.Dense)): [ 0.00626025+0.j 0.07418172+0.j 0.41083747+0.j 0.90866411+0.j]] """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense a = destroy(N, offset=offset) return (alpha * a.dag() - np.conj(alpha) * a).expm(dtype=dtype) @@ -792,7 +800,7 @@ def commutator(A, B, kind="normal"): raise TypeError("Unknown commutator kind '%s'" % kind) -def qutrit_ops(*, dtype=_default_dtype(_data.CSR)): +def qutrit_ops(*, dtype=None): """ Operators for a three level system (qutrit). @@ -810,6 +818,7 @@ def qutrit_ops(*, dtype=_default_dtype(_data.CSR)): """ from .states import qutrit_basis + dtype = dtype or settings.core["default_dtype"] or _data.CSR out = np.empty((6,), dtype=object) one, two, three = qutrit_basis(dtype=dtype) out[0] = one * one.dag() @@ -821,7 +830,7 @@ def qutrit_ops(*, dtype=_default_dtype(_data.CSR)): return out -def phase(N, phi0=0, *, dtype=_default_dtype(_data.Dense)): +def phase(N, phi0=0, *, dtype=None): """ Single-mode Pegg-Barnett phase operator. @@ -847,6 +856,7 @@ def phase(N, phi0=0, *, dtype=_default_dtype(_data.Dense)): The Pegg-Barnett phase operator is Hermitian on a truncated Hilbert space. """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense phim = phi0 + (2 * np.pi * np.arange(N)) / N # discrete phase angles n = np.arange(N)[:, np.newaxis] states = np.array([np.sqrt(kk) / np.sqrt(N) * np.exp(1j * n * kk) @@ -855,7 +865,7 @@ def phase(N, phi0=0, *, dtype=_default_dtype(_data.Dense)): return Qobj(ops, dims=[[N], [N]], type='oper', copy=False).to(dtype) -def enr_destroy(dims, excitations, *, dtype=_default_dtype(_data.CSR)): +def enr_destroy(dims, excitations, *, dtype=None): """ Generate annilation operators for modes in a excitation-number-restricted state space. For example, consider a system consisting of 4 modes, each @@ -901,6 +911,7 @@ def enr_destroy(dims, excitations, *, dtype=_default_dtype(_data.CSR)): quantum system described by dims. """ from .states import enr_state_dictionaries + dtype = dtype or settings.core["default_dtype"] or _data.CSR nstates, state2idx, idx2state = enr_state_dictionaries(dims, excitations) @@ -919,7 +930,7 @@ def enr_destroy(dims, excitations, *, dtype=_default_dtype(_data.CSR)): return [Qobj(a, dims=[dims, dims]).to(dtype) for a in a_ops] -def enr_identity(dims, excitations, *, dtype=_default_dtype(_data.CSR)): +def enr_identity(dims, excitations, *, dtype=None): """ Generate the identity operator for the excitation-number restricted state space defined by the `dims` and `exciations` arguments. See the @@ -949,6 +960,7 @@ def enr_identity(dims, excitations, *, dtype=_default_dtype(_data.CSR)): exication-number-restricted state space defined by `dims` and `exciations`. """ + dtype = dtype or settings.core["default_dtype"] or _data.CSR from .states import enr_state_dictionaries nstates, _, _ = enr_state_dictionaries(dims, excitations) return Qobj(_data.identity[dtype](nstates), @@ -959,7 +971,7 @@ def enr_identity(dims, excitations, *, dtype=_default_dtype(_data.CSR)): copy=False) -def charge(Nmax, Nmin=None, frac=1, *, dtype=_default_dtype(_data.CSR)): +def charge(Nmax, Nmin=None, frac=1, *, dtype=None): """ Generate the diagonal charge operator over charge states from Nmin to Nmax. @@ -989,6 +1001,7 @@ def charge(Nmax, Nmin=None, frac=1, *, dtype=_default_dtype(_data.CSR)): .. versionadded:: 3.2 """ + dtype = dtype or settings.core["default_dtype"] or _data.CSR if Nmin is None: Nmin = -Nmax diag = frac * np.arange(Nmin, Nmax+1, dtype=float) @@ -997,7 +1010,7 @@ def charge(Nmax, Nmin=None, frac=1, *, dtype=_default_dtype(_data.CSR)): return out -def tunneling(N, m=1, *, dtype=_default_dtype(_data.CSR)): +def tunneling(N, m=1, *, dtype=None): r""" Tunneling operator with elements of the form :math:`\\sum |N> 0") return Qobj(_data.identity[dtype](N, scale=1/N), dims=[[N], [N]], @@ -524,7 +526,7 @@ def ket2dm(Q): raise TypeError("Input is not a ket or bra vector.") -def projection(N, n, m, offset=None, *, dtype=_default_dtype(_data.CSR)): +def projection(N, n, m, offset=None, *, dtype=None): r""" The projection operator that projects state :math:`\lvert m\rangle` on state :math:`\lvert n\rangle`. @@ -550,11 +552,12 @@ def projection(N, n, m, offset=None, *, dtype=_default_dtype(_data.CSR)): oper : qobj Requested projection operator. """ + dtype = dtype or settings.core["default_dtype"] or _data.CSR return basis(N, n, offset=offset, dtype=dtype) @ \ basis(N, m, offset=offset, dtype=dtype).dag() -def qstate(string, *, dtype=_default_dtype(_data.Dense)): +def qstate(string, *, dtype=None): r"""Creates a tensor product for a set of qubits in either the 'up' :math:`\lvert0\rangle` or 'down' :math:`\lvert1\rangle` state. @@ -591,6 +594,7 @@ def qstate(string, *, dtype=_default_dtype(_data.Dense)): [ 0.] [ 0.]] """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense n = len(string) if n != (string.count('u') + string.count('d')): raise TypeError('String input to QSTATE must consist ' + @@ -618,7 +622,7 @@ def _character_to_qudit(x): return _qubit_dict[x] if x in _qubit_dict else int(x) -def ket(seq, dim=2, *, dtype=_default_dtype(_data.Dense)): +def ket(seq, dim=2, *, dtype=None): """ Produces a multiparticle ket state for a list or string, where each element stands for state of the respective particle. @@ -696,12 +700,13 @@ def ket(seq, dim=2, *, dtype=_default_dtype(_data.Dense)): [ 0.] [ 0.]] """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense ns = [_character_to_qudit(x) for x in seq] dim = [dim]*len(ns) if isinstance(dim, numbers.Integral) else dim return basis(dim, ns, dtype=dtype) -def bra(seq, dim=2, *, dtype=_default_dtype(_data.Dense)): +def bra(seq, dim=2, *, dtype=None): """ Produces a multiparticle bra state for a list or string, where each element stands for state of the respective particle. @@ -755,6 +760,7 @@ def bra(seq, dim=2, *, dtype=_default_dtype(_data.Dense)): Qobj data = [[ 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]] """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense return ket(seq, dim=dim, dtype=dtype).dag() @@ -872,7 +878,7 @@ def state_index_number(dims, index): return np.unravel_index(index, dims) -def state_number_qobj(dims, state, *, dtype=_default_dtype(_data.Dense)): +def state_number_qobj(dims, state, *, dtype=None): """ Return a Qobj representation of a quantum state specified by the state array `state`. @@ -912,6 +918,7 @@ def state_number_qobj(dims, state, *, dtype=_default_dtype(_data.Dense)): .. note:: Deprecated in QuTiP 5.0, use :func:`basis` instead. """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense warnings.warn("basis() is a drop-in replacement for this", DeprecationWarning) return basis(dims, state, dtype=dtype) @@ -949,7 +956,7 @@ def enr_state_dictionaries(dims, excitations): return nstates, state2idx, idx2state -def enr_fock(dims, excitations, state, *, dtype=_default_dtype(_data.Dense)): +def enr_fock(dims, excitations, state, *, dtype=None): """ Generate the Fock state representation in a excitation-number restricted state space. The `dims` argument is a list of integers that define the @@ -983,6 +990,7 @@ def enr_fock(dims, excitations, state, *, dtype=_default_dtype(_data.Dense)): restricted state space defined by `dims` and `exciations`. """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense nstates, state2idx, _ = enr_state_dictionaries(dims, excitations) try: data =_data.one_element[dtype]((nstates, 1), @@ -996,7 +1004,7 @@ def enr_fock(dims, excitations, state, *, dtype=_default_dtype(_data.Dense)): return Qobj(data, dims=[dims, [1]*len(dims)], type='ket', copy=False) -def enr_thermal_dm(dims, excitations, n, *, dtype=_default_dtype(_data.CSR)): +def enr_thermal_dm(dims, excitations, n, *, dtype=None): """ Generate the density operator for a thermal state in the excitation-number- restricted state space defined by the `dims` and `exciations` arguments. @@ -1029,6 +1037,7 @@ def enr_thermal_dm(dims, excitations, n, *, dtype=_default_dtype(_data.CSR)): dm : Qobj Thermal state density matrix. """ + dtype = dtype or settings.core["default_dtype"] or _data.CSR nstates, _, idx2state = enr_state_dictionaries(dims, excitations) if not isinstance(n, (list, np.ndarray)): n = np.ones(len(dims)) * n @@ -1043,7 +1052,7 @@ def enr_thermal_dm(dims, excitations, n, *, dtype=_default_dtype(_data.CSR)): return out -def phase_basis(N, m, phi0=0, *, dtype=_default_dtype(_data.Dense)): +def phase_basis(N, m, phi0=0, *, dtype=None): """ Basis vector for the mth phase of the Pegg-Barnett phase operator. @@ -1074,13 +1083,14 @@ def phase_basis(N, m, phi0=0, *, dtype=_default_dtype(_data.Dense)): Hilbert space. """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense phim = phi0 + (2.0 * np.pi * m) / N n = np.arange(N)[:, np.newaxis] data = np.exp(1.0j * n * phim) / np.sqrt(N) return Qobj(data, dims=[[N], [1]], type='ket', copy=False).to(dtype) -def zero_ket(N, dims=None, *, dtype=_default_dtype(_data.Dense)): +def zero_ket(N, dims=None, *, dtype=None): """ Creates the zero ket vector with shape Nx1 and dimensions `dims`. @@ -1102,10 +1112,11 @@ def zero_ket(N, dims=None, *, dtype=_default_dtype(_data.Dense)): Zero ket on given Hilbert space. """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense return Qobj(_data.zeros[dtype](N, 1), dims=dims, type='ket', copy=False) -def spin_state(j, m, type='ket', *, dtype=_default_dtype(_data.Dense)): +def spin_state(j, m, type='ket', *, dtype=None): r"""Generates the spin state :math:`\lvert j, m\rangle`, i.e. the eigenstate of the spin-j Sz operator with eigenvalue m. @@ -1129,6 +1140,7 @@ def spin_state(j, m, type='ket', *, dtype=_default_dtype(_data.Dense)): state : qobj Qobj quantum object for spin state """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense J = 2*j + 1 if type == 'ket': @@ -1141,8 +1153,7 @@ def spin_state(j, m, type='ket', *, dtype=_default_dtype(_data.Dense)): raise ValueError(f"Invalid value keyword argument type='{type}'") -def spin_coherent(j, theta, phi, type='ket', - *, dtype=_default_dtype(_data.Dense)): +def spin_coherent(j, theta, phi, type='ket', *, dtype=None): r"""Generate the coherent spin state :math:`\lvert \theta, \phi\rangle`. Parameters @@ -1168,6 +1179,7 @@ def spin_coherent(j, theta, phi, type='ket', state : qobj Qobj quantum object for spin coherent state """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense if type not in ['ket', 'bra', 'dm']: raise ValueError("Invalid value keyword argument 'type'") Sp = jmat(j, '+') @@ -1190,7 +1202,7 @@ def spin_coherent(j, theta, phi, type='ket', '11': np.sqrt(0.5) * (basis([2, 2], [0, 1]) - basis([2, 2], [1, 0])), } -def bell_state(state='00', *, dtype=_default_dtype(_data.Dense)): +def bell_state(state='00', *, dtype=None): r""" Returns the selected Bell state: @@ -1222,10 +1234,11 @@ def bell_state(state='00', *, dtype=_default_dtype(_data.Dense)): Bell_state : qobj Bell state """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense return _BELL_STATES[state].copy().to(dtype) -def singlet_state(*, dtype=_default_dtype(_data.Dense)): +def singlet_state(*, dtype=None): r""" Returns the two particle singlet-state: @@ -1246,10 +1259,11 @@ def singlet_state(*, dtype=_default_dtype(_data.Dense)): Bell_state : qobj :math:`\lvert B_{11}\rangle` Bell state """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense return bell_state('11').to(dtype) -def triplet_states(*, dtype=_default_dtype(_data.Dense)): +def triplet_states(*, dtype=None): r""" Returns a list of the two particle triplet-states: @@ -1278,7 +1292,7 @@ def triplet_states(*, dtype=_default_dtype(_data.Dense)): ] -def w_state(N=3, *, dtype=_default_dtype(_data.Dense)): +def w_state(N=3, *, dtype=None): """ Returns the N-qubit W-state: ``[ |100..0> + |010..0> + |001..0> + ... |000..1> ] / sqrt(n)`` @@ -1305,7 +1319,7 @@ def w_state(N=3, *, dtype=_default_dtype(_data.Dense)): return np.sqrt(1 / N) * state -def ghz_state(N=3, *, dtype=_default_dtype(_data.Dense)): +def ghz_state(N=3, *, dtype=None): """ Returns the N-qubit GHZ-state: ``[ |00...00> + |11...11> ] / sqrt(2)`` diff --git a/qutip/partial_transpose.py b/qutip/partial_transpose.py index 1601bffbcc..61871369d8 100644 --- a/qutip/partial_transpose.py +++ b/qutip/partial_transpose.py @@ -74,7 +74,7 @@ def _partial_transpose_sparse(rho, mask): """ data = sp.lil_matrix((rho.shape[0], rho.shape[1]), dtype=complex) - rho_data = rho.data.as_scipy() + rho_data = rho.to("CSR").data.as_scipy() for m in range(len(rho_data.indptr) - 1): @@ -117,6 +117,6 @@ def _partial_transpose_reference(rho, mask): n_pt = state_number_index( rho.dims[0], np.choose(mask, [psi_B, psi_A])) - A_pt[m_pt, n_pt] = rho.data.as_scipy()[m, n] + A_pt[m_pt, n_pt] = rho.to("CSR").data.as_scipy()[m, n] return Qobj(A_pt, dims=rho.dims) diff --git a/qutip/random_objects.py b/qutip/random_objects.py index 978fc27e9e..be759ae9db 100644 --- a/qutip/random_objects.py +++ b/qutip/random_objects.py @@ -24,7 +24,7 @@ to_super, to_choi, to_chi, to_kraus, to_stinespring) from .core import data as _data from .core.dimensions import flatten -from .core.data.convert import _default_dtype +from . import settings _RAND = default_rng() @@ -211,7 +211,7 @@ def _merge_shuffle_blocks(blocks, generator): def rand_herm(dimensions, density=0.30, distribution="fill", *, - eigenvalues=(), seed=None, dtype=_default_dtype(_data.CSR)): + eigenvalues=(), seed=None, dtype=None): """Creates a random sparse Hermitian quantum object. Parameters @@ -275,13 +275,16 @@ def rand_herm(dimensions, density=0.30, distribution="fill", *, while _data.csr.nnz(out) < 0.95 * nvals: out = _rand_jacobi_rotation(out, generator) out = Qobj(out, type='oper', dims=dims, isherm=True, copy=False) + dtype = dtype or settings.core["default_dtype"] or _data.CSR else: pos_def = distribution == "pos_def" if density < 0.5: M = _rand_herm_sparse(N, density, pos_def, generator) + dtype = dtype or settings.core["default_dtype"] or _data.CSR else: M = _rand_herm_dense(N, density, pos_def, generator) + dtype = dtype or settings.core["default_dtype"] or _data.Dense out = Qobj(M, type='oper', dims=dims, isherm=True, copy=False) @@ -333,7 +336,7 @@ def _rand_herm_dense(N, density, pos_def, generator): def rand_unitary(dimensions, density=1, distribution="haar", *, - seed=None, dtype=_default_dtype(_data.Dense)): + seed=None, dtype=None): r"""Creates a random sparse unitary quantum object. Parameters @@ -367,6 +370,7 @@ def rand_unitary(dimensions, density=1, distribution="haar", *, oper : qobj Unitary quantum operator. """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense N, dims = _implicit_tensor_dimensions(dimensions) if distribution not in ["haar", "exp"]: raise ValueError("distribution must be one of {'haar', 'exp'}") @@ -435,7 +439,7 @@ def _rand_unitary_haar(N, generator): def rand_ket(dimensions, density=1, distribution="haar", *, - seed=None, dtype=_default_dtype(_data.Dense)): + seed=None, dtype=None): """Creates a random ket vector. Parameters @@ -470,6 +474,7 @@ def rand_ket(dimensions, density=1, distribution="haar", *, oper : qobj Ket quantum state vector. """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense generator = _get_generator(seed) N, dims = _implicit_tensor_dimensions(dimensions) if distribution not in ["haar", "fill"]: @@ -496,7 +501,7 @@ def rand_ket(dimensions, density=1, distribution="haar", *, def rand_dm(dimensions, density=0.75, distribution="ginibre", *, eigenvalues=(), rank=None, seed=None, - dtype=_default_dtype(_data.CSR)): + dtype=None): r"""Creates a random density matrix of the desired dimensions. Parameters @@ -543,6 +548,7 @@ def rand_dm(dimensions, density=0.75, distribution="ginibre", *, oper : qobj Density matrix quantum operator. """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense generator = _get_generator(seed) N, dims = _implicit_tensor_dimensions(dimensions) distributions = set(["eigen", "ginibre", "hs", "pure", "herm"]) @@ -623,7 +629,7 @@ def _rand_dm_ginibre(N, rank, generator): def rand_kraus_map(dimensions, *, seed=None, - dtype=_default_dtype(_data.Dense)): + dtype=None): """ Creates a random CPTP map on an N-dimensional Hilbert space in Kraus form. @@ -650,6 +656,7 @@ def rand_kraus_map(dimensions, *, seed=None, N^2 x N x N qobj operators. """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense N, dims = _implicit_tensor_dimensions(dimensions) # Random unitary (Stinespring Dilation) @@ -661,7 +668,7 @@ def rand_kraus_map(dimensions, *, seed=None, def rand_super(dimensions, *, superrep="super", seed=None, - dtype=_default_dtype(_data.Dense)): + dtype=None): """ Returns a randomly drawn superoperator acting on operators acting on N dimensions. @@ -685,6 +692,7 @@ def rand_super(dimensions, *, superrep="super", seed=None, Storage representation. Any data-layer known to `qutip.data.to` is accepted. """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense generator = _get_generator(seed) from .solver.propagator import propagator N, dims = _implicit_tensor_dimensions(dimensions, superoper=True) @@ -703,7 +711,7 @@ def rand_super(dimensions, *, superrep="super", seed=None, def rand_super_bcsz(dimensions, enforce_tp=True, rank=None, *, superrep="super", seed=None, - dtype=_default_dtype(_data.CSR)): + dtype=None): """ Returns a random superoperator drawn from the Bruzda et al ensemble for CPTP maps [BCSZ08]_. Note that due to @@ -746,6 +754,7 @@ def rand_super_bcsz(dimensions, enforce_tp=True, rank=None, *, A superoperator acting on vectorized dim × dim density operators, sampled from the BCSZ distribution. """ + dtype = dtype or settings.core["default_dtype"] or _data.CSR generator = _get_generator(seed) N, dims = _implicit_tensor_dimensions(dimensions, superoper=True) @@ -805,7 +814,7 @@ def rand_super_bcsz(dimensions, enforce_tp=True, rank=None, *, def rand_stochastic(dimensions, density=0.75, kind='left', - *, seed=None, dtype=_default_dtype(_data.CSR)): + *, seed=None, dtype=None): """Generates a random stochastic matrix. Parameters @@ -835,6 +844,7 @@ def rand_stochastic(dimensions, density=0.75, kind='left', oper : qobj Quantum operator form of stochastic matrix. """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense generator = _get_generator(seed) N, dims = _implicit_tensor_dimensions(dimensions) num_elems = max([int(np.ceil(N*(N+1)*density)/2), N]) From 778cbba6833f6472ac1cb4c1c5eea7164cd3d70c Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Sat, 11 Feb 2023 11:34:09 +0100 Subject: [PATCH 057/602] Bump versions of ipython and prompt-toolkit (see dependabot PR #2085). --- doc/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index cb4d830af3..979eb016b4 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -10,7 +10,7 @@ decorator==5.0.7 docutils==0.16 idna==2.10 imagesize==1.2.0 -ipython==7.31.1 +ipython==8.10.0 ipython-genutils==0.2.0 jedi==0.18.0 Jinja2==2.11.3 @@ -24,7 +24,7 @@ parso==0.8.2 pexpect==4.8.0 pickleshare==0.7.5 Pillow==9.3.0 -prompt-toolkit==3.0.18 +prompt-toolkit==3.0.36 ptyprocess==0.7.0 Pygments==2.8.1 pyparsing==2.4.7 From e854789ab8c1eb5b2c66b1caf9b9fe1b89bca15a Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Sat, 11 Feb 2023 11:50:51 +0100 Subject: [PATCH 058/602] Fix typo in path to requirements file. --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index cfd48e0e2d..f5994aeae8 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -15,7 +15,7 @@ build: python: install: - - requirements: docs/requirements.txt + - requirements: doc/requirements.txt - method: pip path: .[full] From 214d61af1284034b46e6277d15d51e57e2e2af46 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Sat, 11 Feb 2023 12:06:48 +0100 Subject: [PATCH 059/602] Update doc requirements SciPy and requests to latest versions. --- doc/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 979eb016b4..bb52a49954 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -30,8 +30,8 @@ Pygments==2.8.1 pyparsing==2.4.7 python-dateutil==2.8.1 pytz==2021.1 -requests==2.25.1 -scipy==1.6.2 +requests==2.28.2 +scipy==1.10.0 six==1.15.0 snowballstemmer==2.1.0 Sphinx==3.5.4 From 0fcd0e2124306e0f726c93e75212f8bff997bf45 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Sat, 11 Feb 2023 12:59:59 +0100 Subject: [PATCH 060/602] Install RTD requirements using conda. --- .readthedocs.yaml | 4 ++- doc/rtd-environment.yml | 54 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 doc/rtd-environment.yml diff --git a/.readthedocs.yaml b/.readthedocs.yaml index f5994aeae8..31e688d547 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -13,9 +13,11 @@ build: tools: python: "3.11" +conda: + environment: doc/rtd-environment.yml + python: install: - - requirements: doc/requirements.txt - method: pip path: .[full] diff --git a/doc/rtd-environment.yml b/doc/rtd-environment.yml new file mode 100644 index 0000000000..b50ee037a4 --- /dev/null +++ b/doc/rtd-environment.yml @@ -0,0 +1,54 @@ +name: rtd-environment +channels: +- conda-forge +dependencies: +- alabaster==0.7.12 +- appnope==0.1.3 +- Babel==2.9.1 +- backcall==0.2.0 +- certifi==2022.12.7 +- chardet==4.0.0 +- cycler==0.10.0 +- Cython==0.29.23 +- decorator==5.0.7 +- docutils==0.16 +- idna==2.10 +- imagesize==1.2.0 +- ipython==8.10.0 +- jedi==0.18.0 +- Jinja2==2.11.3 +- kiwisolver==1.3.1 +- MarkupSafe==1.1.1 +- matplotlib==3.3.4 +- numpy==1.22.0 +- numpydoc==1.1.0 +- packaging==20.9 +- parso==0.8.2 +- pexpect==4.8.0 +- pickleshare==0.7.5 +- Pillow==9.4.0 +- prompt-toolkit==3.0.36 +- ptyprocess==0.7.0 +- Pygments==2.8.1 +- pyparsing==2.4.7 +- python-dateutil==2.8.1 +- pytz==2021.1 +- requests==2.28.2 +- scipy==1.10.0 +- six==1.15.0 +- snowballstemmer==2.1.0 +- Sphinx==3.5.4 +- sphinx-gallery==0.8.2 +- sphinx-rtd-theme==1.1.1 +- sphinxcontrib-applehelp==1.0.2 +- sphinxcontrib-bibtex==2.4.1 +- sphinxcontrib-devhelp==1.0.2 +- sphinxcontrib-htmlhelp==1.0.3 +- sphinxcontrib-jsmath==1.0.1 +- sphinxcontrib-qthelp==1.0.3 +- sphinxcontrib-serializinghtml==1.1.4 +- suitesparse +- traitlets==5.0.5 +- urllib3==1.26.5 +- wcwidth==0.2.5 +- wheel==0.38.1 \ No newline at end of file From e2a9128b41a855ac4e654c998c3e6d76901768e1 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Sat, 11 Feb 2023 13:08:31 +0100 Subject: [PATCH 061/602] Install RTD qutip using conda. --- .readthedocs.yaml | 7 +------ doc/rtd-environment.yml | 5 ++++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 31e688d547..dd78dd1261 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -16,11 +16,6 @@ build: conda: environment: doc/rtd-environment.yml -python: - install: - - method: pip - path: .[full] - sphinx: - configuration: docs/conf.py + configuration: doc/conf.py fail_on_warning: true \ No newline at end of file diff --git a/doc/rtd-environment.yml b/doc/rtd-environment.yml index b50ee037a4..bff96303cd 100644 --- a/doc/rtd-environment.yml +++ b/doc/rtd-environment.yml @@ -51,4 +51,7 @@ dependencies: - traitlets==5.0.5 - urllib3==1.26.5 - wcwidth==0.2.5 -- wheel==0.38.1 \ No newline at end of file +- wheel==0.38.1 +- pip +- pip: + - .[full] \ No newline at end of file From 71c30f7c4c098d8ce7c4d248f6ac6ba8cfeae701 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Sat, 11 Feb 2023 13:13:46 +0100 Subject: [PATCH 062/602] Switch RTD to mambaforge. --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index dd78dd1261..621860399e 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -11,7 +11,7 @@ formats: build: os: ubuntu-22.04 tools: - python: "3.11" + python: "mambaforge-4.10" conda: environment: doc/rtd-environment.yml From 118d30f23b879ebeca928045e2da56e900222a4e Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Sat, 11 Feb 2023 13:23:44 +0100 Subject: [PATCH 063/602] Fix RTD path to qutip. --- doc/rtd-environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/rtd-environment.yml b/doc/rtd-environment.yml index bff96303cd..13a0890dd3 100644 --- a/doc/rtd-environment.yml +++ b/doc/rtd-environment.yml @@ -54,4 +54,4 @@ dependencies: - wheel==0.38.1 - pip - pip: - - .[full] \ No newline at end of file + - ..[full] \ No newline at end of file From cfe91ab0c2dc1dc9530dc844ff23eaf2174a31b1 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Sun, 12 Feb 2023 17:17:23 +0100 Subject: [PATCH 064/602] More carefully manage plot context in all the dynamics guides. --- doc/guide/dynamics/dynamics-bloch-redfield.rst | 9 ++++++--- doc/guide/dynamics/dynamics-floquet.rst | 15 ++++++++++----- doc/guide/dynamics/dynamics-master.rst | 13 +++++++++---- doc/guide/dynamics/dynamics-monte.rst | 7 ++++++- doc/guide/dynamics/dynamics-stochastic.rst | 7 ++++++- doc/guide/dynamics/dynamics-time.rst | 9 +++++++-- 6 files changed, 44 insertions(+), 16 deletions(-) diff --git a/doc/guide/dynamics/dynamics-bloch-redfield.rst b/doc/guide/dynamics/dynamics-bloch-redfield.rst index 5ec31e0e09..892c0a651a 100644 --- a/doc/guide/dynamics/dynamics-bloch-redfield.rst +++ b/doc/guide/dynamics/dynamics-bloch-redfield.rst @@ -6,6 +6,7 @@ Bloch-Redfield master equation .. plot:: + :context: reset :include-source: False import pylab as plt @@ -392,7 +393,9 @@ A full example is: plt.show() -.. plot:: - :context: close-figs - Further examples on time-dependent Bloch-Redfield simulations can be found in the online tutorials. + +.. plot:: + :context: reset + :include-source: false + :nofigs: \ No newline at end of file diff --git a/doc/guide/dynamics/dynamics-floquet.rst b/doc/guide/dynamics/dynamics-floquet.rst index e3d0a3020c..7d3da8c38e 100644 --- a/doc/guide/dynamics/dynamics-floquet.rst +++ b/doc/guide/dynamics/dynamics-floquet.rst @@ -93,7 +93,7 @@ Consider for example the case of a strongly driven two-level atom, described by In QuTiP we can define this Hamiltonian as follows: .. plot:: - :context: close-figs + :context: reset >>> delta = 0.2 * 2*np.pi >>> eps0 = 1.0 * 2*np.pi @@ -107,7 +107,7 @@ In QuTiP we can define this Hamiltonian as follows: The :math:`t=0` Floquet modes corresponding to the Hamiltonian :eq:`eq_driven_qubit` can then be calculated using the :class:`qutip.FloquetBasis` class, which encapsulates the Floquet modes and the quasienergies: .. plot:: - :context: + :context: close-figs >>> T = 2*np.pi / omega >>> floquet_basis = FloquetBasis(H, T, args) @@ -131,7 +131,7 @@ For certain driving amplitudes the quasienergy levels cross. Since the quasienergies can be associated with the time-scale of the long-term dynamics due that the driving, degenerate quasienergies indicates a "freezing" of the dynamics (sometimes known as coherent destruction of tunneling). .. plot:: - :context: + :context: close-figs >>> delta = 0.2 * 2 * np.pi >>> eps0 = 0.0 * 2 * np.pi @@ -175,7 +175,7 @@ The purpose of calculating the Floquet modes is to find the wavefunction solutio To do that, we first need to decompose the initial state in the Floquet states, using the function :meth:`FloquetBasis.to_floquet_basis` .. plot:: - :context: + :context: close-figs >>> psi0 = rand_ket(2) >>> f_coeff = floquet_basis.to_floquet_basis(psi0) @@ -186,7 +186,7 @@ To do that, we first need to decompose the initial state in the Floquet states, and given this decomposition of the initial state in the Floquet states we can easily evaluate the wavefunction that is the solution to :eq:`eq_driven_qubit` at an arbitrary time :math:`t` using the function :meth:`FloquetBasis.from_floquet_basis`: .. plot:: - :context: + :context: close-figs >>> t = 10 * np.random.rand() >>> psi_t = floquet_basis.from_floquet_basis(f_coeff, t) @@ -283,3 +283,8 @@ Finally, :func:`qutip.solver.floquet.fmmesolve` always expects the ``e_ops`` to output = fmmesolve(H, psi0, tlist, [sigmax()], e_ops=[num(2)], spectra_cb=[noise_spectrum], T=T, args=args) p_ex = output.expect[0] + +.. plot:: + :context: reset + :include-source: false + :nofigs: \ No newline at end of file diff --git a/doc/guide/dynamics/dynamics-master.rst b/doc/guide/dynamics/dynamics-master.rst index 2fa7f87d9b..832b971e3f 100644 --- a/doc/guide/dynamics/dynamics-master.rst +++ b/doc/guide/dynamics/dynamics-master.rst @@ -30,7 +30,7 @@ It evolves the state vector and evaluates the expectation values for a set of op For example, the time evolution of a quantum spin-1/2 system with tunneling rate 0.1 that initially is in the up state is calculated, and the expectation values of the :math:`\sigma_z` operator evaluated, with the following code .. plot:: - :context: + :context: reset >>> H = 2*np.pi * 0.1 * sigmax() >>> psi0 = basis(2, 0) @@ -47,7 +47,7 @@ Adding operators to this list results in a larger output list returned by the fu .. plot:: - :context: + :context: close-figs >>> result = sesolve(H, psi0, times, e_ops=[sigmaz(), sigmay()]) >>> result.expect # doctest: +NORMALIZE_WHITESPACE @@ -64,7 +64,7 @@ Adding operators to this list results in a larger output list returned by the fu The resulting list of expectation values can easily be visualized using matplotlib's plotting functions: .. plot:: - :context: + :context: close-figs >>> H = 2*np.pi * 0.1 * sigmax() >>> psi0 = basis(2, 0) @@ -173,7 +173,7 @@ operators ``[sigmaz(), sigmay()]`` to the fifth argument. .. plot:: - :context: + :context: close-figs >>> times = np.linspace(0.0, 10.0, 100) >>> result = mesolve(H, psi0, times, [np.sqrt(0.05) * sigmax()], e_ops=[sigmaz(), sigmay()]) @@ -207,3 +207,8 @@ Now a slightly more complex example: Consider a two-level atom coupled to a leak >>> plt.ylabel('Expectation values') # doctest: +SKIP >>> plt.legend(("cavity photon number", "atom excitation probability")) # doctest: +SKIP >>> plt.show() # doctest: +SKIP + +.. plot:: + :context: reset + :include-source: false + :nofigs: \ No newline at end of file diff --git a/doc/guide/dynamics/dynamics-monte.rst b/doc/guide/dynamics/dynamics-monte.rst index ce05c5e912..9362bffa92 100644 --- a/doc/guide/dynamics/dynamics-monte.rst +++ b/doc/guide/dynamics/dynamics-monte.rst @@ -69,7 +69,7 @@ function for master-equation evolution, except that the initial state must be a To illustrate the use of the Monte Carlo evolution of quantum systems in QuTiP, let's again consider the case of a two-level atom coupled to a leaky cavity. The only differences to the master-equation treatment is that in this case we invoke the :func:`qutip.mcsolve` function instead of :func:`qutip.mesolve` .. plot:: - :context: + :context: reset from qutip.solver.mcsolve import MCSolver, mcsolve @@ -235,3 +235,8 @@ For example, the following code block plots expectation values for 1, 10 and 100 plt.ylabel('Expectation values') plt.legend() plt.show() + +.. plot:: + :context: reset + :include-source: false + :nofigs: \ No newline at end of file diff --git a/doc/guide/dynamics/dynamics-stochastic.rst b/doc/guide/dynamics/dynamics-stochastic.rst index 890a27457b..10ea5aad56 100644 --- a/doc/guide/dynamics/dynamics-stochastic.rst +++ b/doc/guide/dynamics/dynamics-stochastic.rst @@ -122,7 +122,7 @@ Below, we solve the dynamics for an optical cavity at 0K whose output is monitor where :math:`x` is the operator passed using ``m_ops``. The results are available in ``result.measurements``. .. plot:: - :context: close-figs + :context: reset import numpy as np import matplotlib.pyplot as plt @@ -164,3 +164,8 @@ where :math:`x` is the operator passed using ``m_ops``. The results are availabl For other examples on :func:`qutip.stochastic.smesolve`, see the `following notebook `_, as well as these notebooks available at `QuTiP Tutorials page `_: `heterodyne detection `_, `inneficient detection `_, and `feedback control `_. + +.. plot:: + :context: reset + :include-source: false + :nofigs: \ No newline at end of file diff --git a/doc/guide/dynamics/dynamics-time.rst b/doc/guide/dynamics/dynamics-time.rst index 8902d2b94d..6be4dc5154 100644 --- a/doc/guide/dynamics/dynamics-time.rst +++ b/doc/guide/dynamics/dynamics-time.rst @@ -73,7 +73,7 @@ As an example, we will look at a case with a time-dependent Hamiltonian of the f The following code sets up the problem .. plot:: - :context: close-figs + :context: reset ustate = basis(3, 0) excited = basis(3, 1) @@ -107,7 +107,7 @@ The following code sets up the problem Given that we have a single time-dependent Hamiltonian term, and constant collapse terms, we need to specify a single Python function for the coefficient :math:`f(t)`. In this case, one can simply do .. plot:: - :context: + :context: close-figs :nofigs: def H1_coeff(t): @@ -435,3 +435,8 @@ Accessing the state from solver =============================== In QuTiP 4.4 to 4.7, it was possible to request that the solver pass the state, expectation values or collapse operators via arguments to :class:`QobjEvo`. Support for this is not yet available in QuTiP 5. + +.. plot:: + :context: reset + :include-source: false + :nofigs: \ No newline at end of file From 97204d6b166e91ac76f8dee7b6ba9833720b1b05 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Sun, 12 Feb 2023 17:31:21 +0100 Subject: [PATCH 065/602] Remove epub format because it is failing currently. --- .readthedocs.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 621860399e..c5e1295754 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -5,7 +5,6 @@ version: 2 formats: - - epub - pdf build: From 306e0aea7d77ba8e6bd1bce7befcaeda3dce80d6 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Sun, 12 Feb 2023 17:56:53 +0100 Subject: [PATCH 066/602] Fix header in HEOM Bosonic Environments guide. --- doc/guide/heom/bosonic.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/guide/heom/bosonic.rst b/doc/guide/heom/bosonic.rst index 3ca3b6e1df..48856a1187 100644 --- a/doc/guide/heom/bosonic.rst +++ b/doc/guide/heom/bosonic.rst @@ -1,4 +1,4 @@ -solver.heom#################### +#################### Bosonic Environments #################### From 6e4a67f71a2ff6974c89a4b70cc02e827eed63a3 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 13 Feb 2023 13:16:42 +0100 Subject: [PATCH 067/602] Fix formatting of quotes around qutip-jax in changelog for 5.0.0. --- doc/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 071f1e5de9..318c2ec86b 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -63,7 +63,7 @@ significantly to test the data layer API: - ``qutip-tensorflow``: a TensorFlow backend by Asier Galicia (``) - ``qutip-cupy``: a CuPy GPU backend by Felipe Bivort Haiek (``)` - ``qutip-tensornetwork``: a TensorNetwork backend by Asier Galicia (``) -- ``qutip-jax```: a JAX backend by Eric Giguère (``) +- ``qutip-jax``: a JAX backend by Eric Giguère (``) We have also had many other contributors, whose specific contributions are detailed below: From a4eb6331276af48023cdd893ed85c763a258a143 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 13 Feb 2023 13:57:03 +0100 Subject: [PATCH 068/602] Remove extra blank line. --- qutip/solver/heom/bofin_baths.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qutip/solver/heom/bofin_baths.py b/qutip/solver/heom/bofin_baths.py index 4d6f84a1d7..6653a40e75 100644 --- a/qutip/solver/heom/bofin_baths.py +++ b/qutip/solver/heom/bofin_baths.py @@ -123,7 +123,6 @@ def _check_sigma_bar_k_offset(self, type, offset): def _type_is_fermionic(self, type): return type in (self.types["+"], self.types["-"]) - def __init__( self, type, dim, Q, ck, vk, ck2=None, sigma_bar_k_offset=None, tag=None, From 8608116e99b96f19d60c55ce0f49e469584f5725 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 13 Feb 2023 14:01:47 +0100 Subject: [PATCH 069/602] Document new .fermionic attribute of BathExponent. --- qutip/solver/heom/bofin_baths.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qutip/solver/heom/bofin_baths.py b/qutip/solver/heom/bofin_baths.py index 6653a40e75..ff64dfc610 100644 --- a/qutip/solver/heom/bofin_baths.py +++ b/qutip/solver/heom/bofin_baths.py @@ -91,8 +91,11 @@ class BathExponent: Attributes ---------- + fermionic : bool + True if the type of the exponent is a Fermionic type (i.e. either + "+" or "-") and False otherwise. - All of the parameters are available as attributes. + All of the parameters are also available as attributes. """ types = enum.Enum("ExponentType", ["R", "I", "RI", "+", "-"]) From 62eff18b6949d286fffd65d880c00c3d5b76c33d Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 13 Feb 2023 14:22:45 +0100 Subject: [PATCH 070/602] Simplify checks for fermionic and bosonic types and fix formatting errors. --- qutip/solver/heom/bofin_solvers.py | 45 +++++------------------------- 1 file changed, 7 insertions(+), 38 deletions(-) diff --git a/qutip/solver/heom/bofin_solvers.py b/qutip/solver/heom/bofin_solvers.py index b029dea9aa..1af2c5eac9 100644 --- a/qutip/solver/heom/bofin_solvers.py +++ b/qutip/solver/heom/bofin_solvers.py @@ -504,13 +504,6 @@ def heomsolve( Maximum lenght of one internal step. When using pulses, it should be less than half the width of the thinnest pulse. - - - - - - - Returns ------- :class:`~HEOMResult` @@ -688,14 +681,6 @@ def _combine_bath_exponents(self, bath): exponents = [] for b in bath: exponents.extend(b.exponents) - all_bosonic = all( - exp.type in (exp.types.R, exp.types.I, exp.types.RI) - for exp in exponents - ) - all_fermionic = all( - exp.type in (exp.types["+"], exp.types["-"]) - for exp in exponents - ) if not all(exp.Q.dims == exponents[0].Q.dims for exp in exponents): raise ValueError( "All bath exponents must have system coupling operators" @@ -716,18 +701,10 @@ def _grad_n(self, L, he_n): def _grad_prev(self, he_n, k): """ Get the previous gradient. """ - if self.ados.exponents[k].type in ( - BathExponent.types.R, BathExponent.types.I, - BathExponent.types.RI - ): - return self._grad_prev_bosonic(he_n, k) - elif self.ados.exponents[k].type in ( - BathExponent.types["+"], BathExponent.types["-"] - ): + if self.ados.exponents[k].fermionic: return self._grad_prev_fermionic(he_n, k) else: - raise ValueError( - f"Mode {k} has unsupported type {self.ados.exponents[k].type}") + return self._grad_prev_bosonic(he_n, k) def _grad_prev_bosonic(self, he_n, k): if self.ados.exponents[k].type == BathExponent.types.R: @@ -797,18 +774,10 @@ def _grad_prev_fermionic(self, he_n, k): def _grad_next(self, he_n, k): """ Get the previous gradient. """ - if self.ados.exponents[k].type in ( - BathExponent.types.R, BathExponent.types.I, - BathExponent.types.RI - ): - return self._grad_next_bosonic(he_n, k) - elif self.ados.exponents[k].type in ( - BathExponent.types["+"], BathExponent.types["-"] - ): + if self.ados.exponents[k].fermionic: return self._grad_next_fermionic(he_n, k) else: - raise ValueError( - f"Mode {k} has unsupported type {self.ados.exponents[k].type}") + return self._grad_next_bosonic(he_n, k) def _grad_next_bosonic(self, he_n, k): op = _data.mul(self._s_pre_minus_post_Q[k], -1j) @@ -1154,8 +1123,8 @@ def options(self): state_data_type: str, default="dense" Name of the data type of the state used during the ODE evolution. - Use an empty string to keep the input state type. Many integrator can - only work with `Dense`. + Use an empty string to keep the input state type. Many integrators + support only work with `Dense`. store_ados : bool, default=False Whether or not to store the HEOM ADOs. Only relevant when using @@ -1308,7 +1277,7 @@ def add_op(self, row_he, col_he, op): ) def gather(self): - """ Create the HEOM liouvillian from a sorted list of smaller (fast) CSR + """ Create the HEOM liouvillian from a sorted list of smaller sparse matrices. .. note:: From c67a010526a5da96dca3e461f114eb07442b47aa Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 13 Feb 2023 14:23:42 +0100 Subject: [PATCH 071/602] Add simple test for creating a HEOM solver with mixed bosonic and fermionic exponent types and fix PEP8 issues. --- qutip/tests/solver/heom/test_bofin_solvers.py | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/qutip/tests/solver/heom/test_bofin_solvers.py b/qutip/tests/solver/heom/test_bofin_solvers.py index 27db82ac31..aa77072abe 100644 --- a/qutip/tests/solver/heom/test_bofin_solvers.py +++ b/qutip/tests/solver/heom/test_bofin_solvers.py @@ -52,7 +52,7 @@ def assert_raises_steady_state_time_dependent(hsolver): a time-dependent Hamiltonian raises the appropriate exception. """ with pytest.raises(ValueError) as err: - hsolver.steady_state() + hsolver.steady_state() assert str(err.value) == ( "A steady state cannot be determined for a time-dependent" " system" @@ -229,7 +229,8 @@ def test_extract(self): class DrudeLorentzPureDephasingModel: - """ Analytic Drude-Lorentz pure-dephasing model for testing the HEOM solver. + """ Analytic Drude-Lorentz pure-dephasing model for testing the HEOM + solver. """ def __init__(self, lam, gamma, T, Nk): self.lam = lam @@ -266,7 +267,8 @@ def _integrand(omega, t): ] def bath_coefficients(self): - """ Correlation function expansion coefficients for the Drude-Lorentz bath. + """ Correlation function expansion coefficients for the Drude-Lorentz + bath. """ lam, gamma, T = self.lam, self.gamma, self.T Nk = self.Nk @@ -284,7 +286,8 @@ def bath_coefficients(self): class UnderdampedPureDephasingModel: - """ Analytic Drude-Lorentz pure-dephasing model for testing the HEOM solver. + """ Analytic Drude-Lorentz pure-dephasing model for testing the HEOM + solver. """ def __init__(self, lam, gamma, w0, T, Nk): self.lam = lam @@ -541,14 +544,27 @@ def test_create_fermionic(self): assert hsolver.ados.exponents == exponents * 3 assert hsolver.ados.max_depth == 2 - def test_create_bath_errors(self): + def test_create_mixed_bosonic_and_fermionic(self): Q = sigmaz() H = sigmax() - mixed_types = [ + exponents = [ BathExponent("+", 2, Q=Q, ck=1.1, vk=2.1, sigma_bar_k_offset=1), BathExponent("-", 2, Q=Q, ck=1.2, vk=2.2, sigma_bar_k_offset=-1), BathExponent("R", 2, Q=Q, ck=1.2, vk=2.2), ] + bath = Bath(exponents) + + hsolver = HEOMSolver(H, bath, 2) + assert hsolver.ados.exponents == exponents + assert hsolver.ados.max_depth == 2 + + hsolver = HEOMSolver(H, [bath] * 3, 2) + assert hsolver.ados.exponents == exponents * 3 + assert hsolver.ados.max_depth == 2 + + def test_create_bath_errors(self): + Q = sigmaz() + H = sigmax() mixed_q_dims = [ BathExponent("I", 2, Q=tensor(Q, Q), ck=1.2, vk=2.2), BathExponent("R", 2, Q=Q, ck=1.2, vk=2.2), @@ -659,7 +675,6 @@ def test_pure_dephasing_model_bosonic_bath( else: assert_raises_steady_state_time_dependent(hsolver) - @pytest.mark.parametrize(['terminator'], [ pytest.param(True, id="terminator"), pytest.param(False, id="noterminator"), @@ -767,7 +782,6 @@ def test_discrete_level_model_fermionic_bath(self, evo, liouvillianize): else: assert_raises_steady_state_time_dependent(hsolver) - @pytest.mark.parametrize(['bath_cls', 'analytic_current'], [ pytest.param(LorentzianBath, 0.001101, id="matsubara"), pytest.param(LorentzianPadeBath, 0.000813, id="pade"), @@ -805,7 +819,6 @@ def test_discrete_level_model_lorentzian_baths( # analytic_current = dlm.analytic_current() np.testing.assert_allclose(analytic_current, current, rtol=1e-3) - @pytest.mark.parametrize(['evo'], [ pytest.param("qobj"), pytest.param("qobjevo_const"), @@ -838,7 +851,9 @@ def test_discrete_level_model_fermionic_bath_with_decoupled_bosonic_bath( # the interaction between the system and the fermionic bath: eps = [1e-10] * 5 bosonic_Q = sigmax() - bosonic_bath = BosonicBath(bosonic_Q, eps, eps, eps, eps, combine=False) + bosonic_bath = BosonicBath( + bosonic_Q, eps, eps, eps, eps, combine=False, + ) # for a single impurity we converge with max_depth = 2 # we specify the bosonic bath first to ensure that the test checks # that the sums inside HEOMSolver grad-next/prev work when the bosonic @@ -861,8 +876,6 @@ def test_discrete_level_model_fermionic_bath_with_decoupled_bosonic_bath( else: assert_raises_steady_state_time_dependent(hsolver) - - @pytest.mark.parametrize(['ado_format'], [ pytest.param("hierarchy-ados-state", id="hierarchy-ados-state"), pytest.param("numpy", id="numpy"), @@ -972,8 +985,8 @@ def test_heomsolve_with_pure_dephasing_model( options = {"nsteps": 15000, "store_states": True} e_ops = { - "11": basis(2,0) * basis(2,0).dag(), - "22": basis(2,1) * basis(2,1).dag(), + "11": basis(2, 0) * basis(2, 0).dag(), + "22": basis(2, 1) * basis(2, 1).dag(), } tlist = np.linspace(0, 10, 21) @@ -1003,7 +1016,9 @@ class TestHSolverDL: pytest.param("qobjevo_const", True, id="qobjevo-const-combined"), pytest.param("listevo_const", True, id="listevo-const-combined"), pytest.param("qobjevo_timedep", True, id="qobjevo-timedep-combined"), - pytest.param("qobjevo_timedep", False, id="qobjevo-timedep-uncombined"), + pytest.param( + "qobjevo_timedep", False, id="qobjevo-timedep-uncombined", + ), ]) @pytest.mark.parametrize(['liouvillianize'], [ pytest.param(False, id="hamiltonian"), From 3254bada85cbcef89f6c17a45a76cd59327a091e Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Mon, 13 Feb 2023 15:18:55 +0100 Subject: [PATCH 072/602] Update documentation to reflect that mixtures of bosonic and fermionic baths are supported. --- doc/guide/heom/history.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/guide/heom/history.rst b/doc/guide/heom/history.rst index 1328a1e949..dedcdf4922 100644 --- a/doc/guide/heom/history.rst +++ b/doc/guide/heom/history.rst @@ -46,12 +46,12 @@ at https://github.com/tehruhn/bofin/tree/main/examples. Current implementation ---------------------- -The current implementation is a rewrite of BoFiN in pure Python. It's -right-hand side construction has similar speed to BoFiN-fast, but is written -in pure Python. Built-in implementations of a variety of different baths -are provided, and a single solver is used for both fermionic and bosonic baths. -Multiple baths of the same kind (either fermionic or bosonic) may be -specified in a single problem, and there is good support for working with -the auxiliary density operator (ADO) state and extracting information from it. +The current implementation is a rewrite of BoFiN in pure Python. It's right-hand +side construction has similar speed to BoFiN-fast, but is written in pure +Python. Built-in implementations of a variety of different baths are provided, +and a single solver is used for both fermionic and bosonic baths. Multiple baths +of either the same kind, or a mixture of fermionic and bosonic baths, may be +specified in a single problem, and there is good support for working with the +auxiliary density operator (ADO) state and extracting information from it. The code was written by Neill Lambert and Simon Cross. From 67b8b241c23b9975a028d35888688bb30726302a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 13 Feb 2023 15:39:54 -0500 Subject: [PATCH 073/602] Test svd sign independant --- qutip/tests/core/data/test_linalg.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qutip/tests/core/data/test_linalg.py b/qutip/tests/core/data/test_linalg.py index 01681d4e23..50729bba9c 100644 --- a/qutip/tests/core/data/test_linalg.py +++ b/qutip/tests/core/data/test_linalg.py @@ -104,8 +104,14 @@ def test_mathematically_correct_svd(self, shape): only_S = _data.svd(matrix, False) assert sum(test_S > 1e-10) == 6 - np.testing.assert_allclose(test_U.to_array(), u, atol=1e-7, rtol=1e-7) - np.testing.assert_allclose(test_V.to_array(), v, atol=1e-7, rtol=1e-7) + # columns are definied up to a sign + np.testing.assert_allclose( + np.abs(test_U.to_array()), np.abs(u), atol=1e-7, rtol=1e-7 + ) + # rows are definied up to a sign + np.testing.assert_allclose( + np.abs(test_V.to_array()), np.abs(v), atol=1e-7, rtol=1e-7 + ) np.testing.assert_allclose(test_S, s, atol=1e-7, rtol=1e-7) np.testing.assert_allclose(only_S, s, atol=1e-7, rtol=1e-7) From 908756f359ff2ba06852e0ac3921be1f55e4aa95 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Tue, 14 Feb 2023 10:50:10 +0100 Subject: [PATCH 074/602] Add links and badges for Read the Docs. --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 19a901e5da..4bd0de39da 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ and [J. R. Johansson](https://github.com/jrjohansson) [![Build Status](https://github.com/qutip/qutip/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/qutip/qutip/actions/workflows/tests.yml) [![Coverage Status](https://img.shields.io/coveralls/qutip/qutip.svg?logo=Coveralls)](https://coveralls.io/r/qutip/qutip) [![Maintainability](https://api.codeclimate.com/v1/badges/df502674f1dfa1f1b67a/maintainability)](https://codeclimate.com/github/qutip/qutip/maintainability) -[![license](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) +[![license](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![PyPi Downloads](https://img.shields.io/pypi/dm/qutip?label=downloads%20%7C%20pip&logo=PyPI)](https://pypi.org/project/qutip) [![Conda-Forge Downloads](https://img.shields.io/conda/dn/conda-forge/qutip?label=downloads%20%7C%20conda&logo=Conda-Forge)](https://anaconda.org/conda-forge/qutip) @@ -86,12 +86,18 @@ This version should be fully working. If you find any bugs, confusing documentat Documentation ------------- +[![Documentation Status - Latest](https://readthedocs.org/projects/qutip/badge/?version=latest)](https://qutip.readthedocs.io/en/latest/?badge=latest) +[![Documentation Status - Master](https://readthedocs.org/projects/qutip/badge/?version=master)](https://qutip.readthedocs.io/en/latest/?badge=master) + +The documentation for the latest [stable release](https://qutip.readthedocs.io/en/latest/) and the [master](https://qutip.readthedocs.io/en/master/) branch is available for reading on Read The Docs. + The documentation for official releases, in HTML and PDF formats, can be found in the [documentation section of the QuTiP website](https://qutip.org/documentation.html). + The latest development documentation is available in this repository in the `doc` folder. A [selection of demonstration notebooks is available](https://qutip.org/tutorials.html), which demonstrate some of the many features of QuTiP. -These are stored in the [qutip/qutip-notebooks repository](https://github.com/qutip/qutip-notebooks) here on GitHub. -You can run the notebooks online using myBinder: [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/qutip/qutip-notebooks/master?filepath=index.ipynb) +These are stored in the [qutip/qutip-tutorials repository](https://github.com/qutip/qutip-tutorials) here on GitHub. + Contribute ---------- From 84d7a9888d6d7fc58f063fe6c74a6eff09317504 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Tue, 14 Feb 2023 10:56:21 +0100 Subject: [PATCH 075/602] Remove second docs badge -- it is just confusing because the image is identical for both. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4bd0de39da..376e25b3a8 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,6 @@ Documentation ------------- [![Documentation Status - Latest](https://readthedocs.org/projects/qutip/badge/?version=latest)](https://qutip.readthedocs.io/en/latest/?badge=latest) -[![Documentation Status - Master](https://readthedocs.org/projects/qutip/badge/?version=master)](https://qutip.readthedocs.io/en/latest/?badge=master) The documentation for the latest [stable release](https://qutip.readthedocs.io/en/latest/) and the [master](https://qutip.readthedocs.io/en/master/) branch is available for reading on Read The Docs. From 0426f8a0da322700df3fa89f29b80d8e89666794 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 14 Feb 2023 13:10:01 -0500 Subject: [PATCH 076/602] Fix requirement --- qutip/tests/test_bloch.py | 11 +++++------ setup.cfg | 4 ++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/qutip/tests/test_bloch.py b/qutip/tests/test_bloch.py index f736090c2e..33e8fb2fae 100644 --- a/qutip/tests/test_bloch.py +++ b/qutip/tests/test_bloch.py @@ -9,14 +9,11 @@ try: import matplotlib.pyplot as plt from matplotlib.testing.decorators import check_figures_equal + import IPython + check_pngs_equal = check_figures_equal(extensions=["png"]) except ImportError: - def check_figures_equal(*args, **kw): - def _error(*args, **kw): - raise RuntimeError("matplotlib is not installed") plt = None - - -check_pngs_equal = check_figures_equal(extensions=["png"]) + check_pngs_equal = pytest.mark.skip(reason="matplotlib not installed") class RefBloch(Bloch): @@ -474,6 +471,8 @@ def test_vector_errors_color_length(self, vectors, colors): def test_repr_svg(): + pytest.importorskip("matplotlib") + pytest.importorskip("ipython") svg = Bloch()._repr_svg_() assert isinstance(svg, str) assert svg.startswith(" Date: Tue, 14 Feb 2023 19:26:40 -0500 Subject: [PATCH 077/602] working tests --- qutip/core/data/matmul.pyx | 3 +- qutip/solver/sode/_sode.pyx | 5 +- qutip/solver/sode/check.py | 32 ---------- qutip/solver/sode/ssystem.pyx | 14 ++-- qutip/tests/solver/test_sode_method.py | 67 ++++++++++++++++++++ qutip/tests/solver/test_stochastic_system.py | 29 ++++++--- 6 files changed, 99 insertions(+), 51 deletions(-) delete mode 100644 qutip/solver/sode/check.py create mode 100644 qutip/tests/solver/test_sode_method.py diff --git a/qutip/core/data/matmul.pyx b/qutip/core/data/matmul.pyx index 0accc05286..9429a05f37 100644 --- a/qutip/core/data/matmul.pyx +++ b/qutip/core/data/matmul.pyx @@ -196,7 +196,8 @@ cpdef Dense matmul_csr_dense_dense(CSR left, Dense right, "out matrix is {}-ordered".format('Fortran' if out.fortran else 'C') + " but input is {}-ordered".format('Fortran' if right.fortran else 'C') ) - warnings.warn(msg, dense.OrderEfficiencyWarning) + from qutip.core.data.dense import OrderEfficiencyWarning + warnings.warn(msg, OrderEfficiencyWarning) # Rather than making loads of copies of the same code, we just moan at # the user and then transpose one of the arrays. We prefer to have # `right` in Fortran-order for cache efficiency. diff --git a/qutip/solver/sode/_sode.pyx b/qutip/solver/sode/_sode.pyx index 470db90392..34665961ec 100644 --- a/qutip/solver/sode/_sode.pyx +++ b/qutip/solver/sode/_sode.pyx @@ -16,7 +16,10 @@ cdef class Euler: self.system = system @cython.wraparound(False) - def run(self, double t, Data state, double dt, double[:, :, ::1] dW, int ntraj): + def run( + self, double t, Data state, double dt, + double[:, :, ::1] dW, int ntraj + ): cdef int i cdef Dense out for i in range(ntraj): diff --git a/qutip/solver/sode/check.py b/qutip/solver/sode/check.py deleted file mode 100644 index 39193220a8..0000000000 --- a/qutip/solver/sode/check.py +++ /dev/null @@ -1,32 +0,0 @@ -import numpy as np -from itertools import product -from qutip.core import data as _data -import qutip - -def compute_step(system, method, state, ts, noise, **kw): - import qutip.solver.sode._sode as _sode - stepper = getattr(_sode, method)(system, **kw) - out = [] - for t in ts: - out.append(stepper.run(0, state.copy(), t, noise.dW(t), 1)) - return out - - -def get_error_order(system, method): - num_runs = 10 - ts = [ - 0.000001, 0.000002, 0.000005, 0.00001, 0.00002, 0.00005, - 0.0001, 0.0002, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, - ] - state = rand_ket(system.dims[0]).data - err = np.zeros(len(ts), dtype=float) - for _ in range(num_runs): - noise = MultiNoise(0.1, 0.000001) - out = compute_step(system, method, state, ts, noise) - - for i, (o, t) in enumerate(zip(out, ts)): - got = o.to_array()[0,0] - target = system.analytic(t, noise.dw(t)[0,0]) - err[i] += (np.abs(got - target)) - err /= num_runs - return np.polyfit(np.log(ts), np.log(err), 1) diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index 8fb42a68f9..4a7b29502a 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -189,14 +189,14 @@ cdef class StochasticOpenSystem(_StochasticSystem): for i in range(self.num_collapse): c_op = self.c_ops[i] vec = c_op.matmul_data(t, state) - expect = _data.trace_oper_ket_dense(vec) + expect = _data.trace_oper_ket(vec) out.append(_data.add(vec, state, -expect)) return out cpdef void set_state(self, double t, Data state): cdef n, l self.t = t - self.state = _data.to(_data.Dense, state) + self.state = _data.to(_data.Dense, state).reorder(fortran=1) self._a_set = False self._b_set = False self._Lb_set = False @@ -471,10 +471,10 @@ cdef class SimpleStochasticSystem(_StochasticSystem): def analytic(self, t, W): """ - Analytic solution, H and all c_ops must commute. + Analytic solution, H and all c_ops must commute and are constant. """ - out = self.H.full() * t + out = self.H(0) * t for i in range(self.num_collapse): - out += self.c_ops[i].full() * W[i] - out -= 0.5 * self.c_ops[i].full() @ self.c_ops[i].full() * t - return np.exp(out) + out += self.c_ops[i](0) * W[i] + out -= 0.5 * self.c_ops[i](0) @ self.c_ops[i](0) * t + return out.expm().data diff --git a/qutip/tests/solver/test_sode_method.py b/qutip/tests/solver/test_sode_method.py new file mode 100644 index 0000000000..5d3436d08a --- /dev/null +++ b/qutip/tests/solver/test_sode_method.py @@ -0,0 +1,67 @@ +import numpy as np +from itertools import product +from qutip.core import data as _data +from qutip import qeye, destroy, QobjEvo, rand_ket +import qutip.solver.sode._sode as _sode +import pytest +from qutip.solver.sode.ssystem import SimpleStochasticSystem +from qutip.solver.sode.ito import MultiNoise + + +def get_error_order(system, state, method, plot=False, **kw): + stepper = getattr(_sode, method)(system, **kw) + num_runs = 10 + ts = [ + 0.000001, 0.000002, 0.000005, 0.00001, 0.00002, 0.00005, + 0.0001, 0.0002, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, + ] + # state = rand_ket(system.dims[0]).data + err = np.zeros(len(ts), dtype=float) + for _ in range(num_runs): + noise = MultiNoise(0.1, 0.000001, system.num_collapse) + for i, t in enumerate(ts): + out = stepper.run(0, state.copy(), t, noise.dW(t), 1) + target = system.analytic(t, noise.dw(t)[0]) @ state + err[i] += _data.norm.l2(out - target) + + err /= num_runs + if plot: + import matplotlib.pyplot as plt + plt.loglog(ts, err) + return np.polyfit(np.log(ts), np.log(err), 1)[0] + + +def _make_oper(kind, N): + if kind == "qeye": + out = qeye(N) * np.random.rand() + elif kind == "destroy": + out = destroy(N) * np.random.rand() + elif kind == "destroy2": + out = destroy(N)**2 * np.random.rand() + return QobjEvo(out) + + +@pytest.mark.parametrize(["method", "order", "kw"], [ + pytest.param("Euler", 0.5, {}, id="Euler"), + pytest.param("Milstein", 1.0, {}, id="Milstein"), + pytest.param("Platen", 1.0, {}, id="Platen"), + pytest.param("PredCorr", 1.0, {}, id="PredCorr"), + pytest.param("PredCorr", 1.0, {"alpha": 0.5}, id="PredCorr_0.5"), + pytest.param("Taylor15", 1.5, {}, id="Taylor15"), + pytest.param("Explicit15", 1.5, {}, id="Explicit15"), +]) +@pytest.mark.parametrize(['H', 'c_ops'], [ + pytest.param("qeye", ["destroy"], id='simple'), + pytest.param("destroy", ["destroy"], id='simple'), + pytest.param("qeye", ["qeye", "destroy", "destroy2"], id='2 c_ops'), +]) +def test_methods(H, c_ops, method, order, kw): + N = 5 + H = _make_oper(H, N) + c_ops = [_make_oper(op, N) for op in c_ops] + system = SimpleStochasticSystem(H, c_ops) + state = rand_ket(N).data + error_order = get_error_order(system, state, method, **kw) + # The first error term of the method is dt**0.5 greater than the solver + # order. + assert (order + 0.35) < error_order diff --git a/qutip/tests/solver/test_stochastic_system.py b/qutip/tests/solver/test_stochastic_system.py index 4925ec043f..455e2fd35d 100644 --- a/qutip/tests/solver/test_stochastic_system.py +++ b/qutip/tests/solver/test_stochastic_system.py @@ -1,8 +1,12 @@ import numpy as np -from qutip import qeye, num, destroy, create, QobjEvo, basis, rand_herm +from qutip import ( + qeye, num, destroy, create, QobjEvo, + basis, rand_herm, fock_dm, liouvillian, operator_to_vector +) from qutip.solver.sode.ssystem import * -import qutip.data as _data +import qutip.core.data as _data import pytest +from itertools import product def L0(system, f): @@ -89,12 +93,15 @@ def _check_equivalence(f, target, args): Check that the error is proportional to `dt`. """ dts = np.logspace(-5, -1, 9) - errors_dt = [ - _data.norm.l2(f(*args, dt=dt) - target) / dt + errors_dt = np.array([ + _data.norm.l2(f(*args, dt=dt) - target) for dt in dts - ] - poly = np.polyfit(dts, errors_dt, 1) - return -1 < poly[0] < 1 + ]) + poly = np.polyfit(np.log(dts), np.log(errors_dt+1e-16), 1)[0] + if poly > 1.05: + print(poly, np.log(errors_dt)) + print(dts, errors_dt) + return poly < 1.05 def _run_derr_check(solver, state): @@ -106,7 +113,7 @@ def _run_derr_check(solver, state): a = solver.drift solver.set_state(t, state) - assert solver.drift(t, state) == solver.a() + assert _data.norm.l2(solver.drift(t, state) - solver.a()) < 1e-6 assert _check_equivalence(L0(solver, a), solver.L0a(), (t, state)) for i in range(N): @@ -150,7 +157,7 @@ def _make_oper(kind, N): pytest.param("qeye", ["td"], id='c_ops td'), pytest.param("rand", ["rand"], id='random'), ]) -@pytest.mark.parametrize('type', ["sse", "sme"]) +@pytest.mark.parametrize('type', ["sme"]) # "sse" @pytest.mark.parametrize('heterodyne', [False, True]) def test_system(H, c_ops, type, heterodyne): N = 5 @@ -158,7 +165,9 @@ def test_system(H, c_ops, type, heterodyne): c_ops = [_make_oper(op, N) for op in c_ops] if type == "sse": system = StochasticClosedSystem(H, c_ops, heterodyne) + state = basis(N, N-2).data else: + H = liouvillian(H) system = StochasticOpenSystem(H, c_ops, heterodyne) - state = basis(N, N-2).data + state = operator_to_vector(fock_dm(N, N-2)).data _run_derr_check(system, state) From e36536a82bffcd76823a6f4c21bbcfff98f74199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Wed, 15 Feb 2023 10:04:02 -0500 Subject: [PATCH 078/602] Apply suggestions from code review Co-authored-by: Simon Cross --- setup.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 70ee4ea220..1eb1f11cd2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -56,7 +56,7 @@ tests = pytest-rerunfailures ipython = ipython -extra = +extras = loky tqdm ; This uses ConfigParser's string interpolation to include all the above @@ -67,4 +67,4 @@ full = %(semidefinite)s %(tests)s %(ipython)s - %(extra)s + %(extras)s From 6663bc8d33f5c1b275201625973cfaf3c6f892c5 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Wed, 15 Feb 2023 19:29:59 +0100 Subject: [PATCH 079/602] Add a HEOM test for combined bosonic and fermionic baths using a system consisting of a spin, discrete level fermionic leads, and a damped bosonic mode. --- qutip/tests/solver/heom/test_bofin_solvers.py | 193 ++++++++++++++++-- 1 file changed, 180 insertions(+), 13 deletions(-) diff --git a/qutip/tests/solver/heom/test_bofin_solvers.py b/qutip/tests/solver/heom/test_bofin_solvers.py index aa77072abe..c168070da9 100644 --- a/qutip/tests/solver/heom/test_bofin_solvers.py +++ b/qutip/tests/solver/heom/test_bofin_solvers.py @@ -8,7 +8,7 @@ from scipy.integrate import quad from qutip import ( - basis, destroy, expect, liouvillian, sigmax, sigmaz, + basis, destroy, expect, liouvillian, qeye, sigmax, sigmaz, tensor, Qobj, QobjEvo ) from qutip.core import data as _data @@ -327,29 +327,89 @@ def _integrand(omega, t): ] +class BosonicMode: + """ A description of a bosonic mode for inclusion in a + DiscreteLevelCurrentModel. + """ + def __init__(self, N, Lambda, Omega, gamma_b): + self.N = N + self.Lambda = Lambda + self.Omega = Omega + self.gamma_b = gamma_b + + def bath_coefficients(self): + ck_real = [0.5 * self.Lambda**2, 0.5 * self.Lambda**2] + vk_real = [0.5 * 1.0j * self.Lambda**2, -0.5 * 1.0j * self.Lambda**2] + + ck_imag = [ + -1.0j * self.Omega + self.gamma_b / 2, + 1.0j * self.Omega + self.gamma_b / 2, + ] + vk_imag = [ + -1.0j * self.Omega + self.gamma_b / 2, + 1.0j * self.Omega + self.gamma_b / 2, + ] + return ck_real, ck_imag, vk_real, vk_imag + + class DiscreteLevelCurrentModel: """ Analytic discrete level current model for testing the HEOM solver - with a fermionic bath. + with a fermionic bath (and optionally a bosonic mode). """ - def __init__(self, gamma, W, T, lmax): + def __init__(self, gamma, W, T, lmax, theta=2., e1=1., bosonic_mode=None): + # single fermion + self.e1 = e1 # energy + + # parameters for the fermionic leads self.gamma = gamma self.W = W self.T = T self.lmax = lmax # Pade cut-off self.beta = 1. / T + self.theta = theta # bias - # single fermion - self.e1 = 1. - d1 = destroy(2) - self.H = self.e1 * d1.dag() * d1 - self.Q = d1 + # bosonic_mode + self.bosonic_mode = bosonic_mode - # bias - self.theta = 2. + # Construct Hamiltonian and coupling operator + if self.bosonic_mode is None: + d1 = destroy(2) + self.H = self.e1 * d1.dag() @ d1 + self.Q = d1 + self._sys_occupation_op = d1.dag() @ d1 + else: + d1 = destroy(2) & qeye(self.bosonic_mode.N) + a = qeye(2) & destroy(self.bosonic_mode.N) + self.H = ( + self.e1 * d1.dag() @ d1 + + self.bosonic_mode.Omega * a.dag() @ a + + self.bosonic_mode.Lambda * (a + a.dag()) @ d1.dag() @ d1 + ) + if self.bosonic_mode.gamma_b != 0: + # apply phenomenological damping: + self.H = liouvillian( + self.H, [np.sqrt(bosonic_mode.gamma_b) * a], + ) + self.Q = d1 + self._sys_occupation_op = d1.dag() @ d1 - def rho(self): - """ Initial state. """ - return 0.5 * Qobj(np.ones((2, 2))) + def rho(self, rho_fermion=None): + """ Return initial system density matrix given the density matrix for + the single Fermionic mode. + """ + if rho_fermion is None: + rho_fermion = 0.5 * Qobj(np.ones((2, 2))) + elif rho_fermion.isket: + rho_fermion = rho_fermion.proj() + if self.bosonic_mode is None: + rho = rho_fermion + else: + bm0 = basis(self.bosonic_mode.N, 0) + rho = rho_fermion & (bm0 @ bm0.dag()) + return rho + + def sys_occupation(self, state): + return expect(state, self._sys_occupation_op) def state_current(self, ado_state, tags=None): level_1_aux = [ @@ -372,6 +432,12 @@ def exp_op(exp): ) def analytic_current(self): + if self.bosonic_mode is not None: + raise RuntimeError( + "Analytic calculation of the current is not implemented in the" + " case where a bosonic mode is present." + ) + Gamma, W, beta, e1 = self.gamma, self.W, self.beta, self.e1 mu_l = self.theta / 2. mu_r = - self.theta / 2. @@ -876,6 +942,107 @@ def test_discrete_level_model_fermionic_bath_with_decoupled_bosonic_bath( else: assert_raises_steady_state_time_dependent(hsolver) + @pytest.mark.parametrize(['evo'], [ + pytest.param("qobj"), + pytest.param("qobjevo_const"), + pytest.param("qobjevo_timedep"), + ]) + @pytest.mark.parametrize(['liouvillianize'], [ + pytest.param(False, id="hamiltonian"), + pytest.param(True, id="liouvillian"), + ]) + def test_discrete_level_model_fermionic_bath_with_coupled_bosonic_bath( + self, evo, liouvillianize + ): + dlm = DiscreteLevelCurrentModel( + gamma=0.01, W=1, T=0.5, lmax=1, e1=0.3, theta=0.5, + ) + bosonic_mode = BosonicMode( + N=4, Omega=0.2, Lambda=0.1, gamma_b=0.1, + ) + + dlm_ref = DiscreteLevelCurrentModel( + gamma=0.01, W=1, T=0.5, lmax=1, e1=0.3, theta=0.5, + bosonic_mode=bosonic_mode, + ) + + options = { + "store_states": True, + "store_ados": True, + "nsteps": 15_000, + "rtol": 1e-7, + "atol": 1e-7, + } + + # First we construct a solver with the boson modelled as part of the + # system and only a single Fermionic bath. This will provide the + # reference result for the test: + fermionic_bath_ref = FermionicBath( + dlm_ref.Q, *dlm_ref.bath_coefficients(), tag="fermionic", + ) + + hsolver_ref = HEOMSolver( + dlm_ref.H, [fermionic_bath_ref], 2, options=options, + ) + + # Then we construct a solver for the same system, but with the + # bosonic mode as a bath This is the result we would like to check: + H_sys = hamiltonian_to_sys(dlm.H, evo, liouvillianize) + + fermionic_bath = FermionicBath( + dlm.Q, *dlm.bath_coefficients(), tag="fermionic", + ) + + bosonic_bath = BosonicBath( + dlm.Q.dag() @ dlm.Q, *bosonic_mode.bath_coefficients(), + combine=True, tag="bosonic", + ) + + hsolver = HEOMSolver( + H_sys, [bosonic_bath, fermionic_bath], 4, options=options, + ) + + # Calculate currents and occupations: + tlist = np.linspace(0, 1000, 300) + psi0 = basis(2, 0) + + result_ref = hsolver_ref.run(dlm_ref.rho(psi0), tlist) + current_ref = [ + dlm_ref.state_current(ado_state, tags=["fermionic"]) + for ado_state in result_ref.ado_states + ] + sys_occupation_ref = dlm_ref.sys_occupation( + result_ref.states + ) + + result = hsolver.run(dlm.rho(psi0), tlist) + current = [ + dlm.state_current(ado_state, tags=["fermionic"]) + for ado_state in result.ado_states + ] + sys_occupation = dlm.sys_occupation(result.states) + + np.testing.assert_allclose(current_ref, current, atol=1e-3) + np.testing.assert_allclose( + sys_occupation_ref, sys_occupation, atol=1e-3, + ) + + if evo != "qobjevo_timedep": + rho_final_ref, ado_state_ref = hsolver_ref.steady_state() + current_ss_ref = dlm_ref.state_current(ado_state_ref) + sys_occupation_ss_ref = dlm_ref.sys_occupation(rho_final_ref) + + rho_final, ado_state = hsolver.steady_state() + current_ss = dlm.state_current(ado_state) + sys_occupation_ss = dlm.sys_occupation(rho_final) + + np.testing.assert_allclose(current_ss_ref, current_ss, rtol=1e-3) + np.testing.assert_allclose( + sys_occupation_ss_ref, sys_occupation_ss, rtol=1e-3, + ) + else: + assert_raises_steady_state_time_dependent(hsolver) + @pytest.mark.parametrize(['ado_format'], [ pytest.param("hierarchy-ados-state", id="hierarchy-ados-state"), pytest.param("numpy", id="numpy"), From 7f24ba4ce701dacf78e113e1e01f8fa38ffc3cc3 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 15 Feb 2023 17:26:55 -0500 Subject: [PATCH 080/602] smesolve work --- qutip/solver/__init__.py | 1 + qutip/solver/mcsolve.py | 2 +- qutip/solver/sode/__init__.py | 2 + qutip/solver/sode/_sode.pyx | 11 ++-- qutip/solver/sode/sode.py | 29 +++++++---- qutip/solver/sode/ssystem.pyx | 17 +----- qutip/solver/stochastic.py | 97 +++++++++++++++++++++++++---------- 7 files changed, 100 insertions(+), 59 deletions(-) diff --git a/qutip/solver/__init__.py b/qutip/solver/__init__.py index 99d84ccd88..3ec68b68b5 100644 --- a/qutip/solver/__init__.py +++ b/qutip/solver/__init__.py @@ -16,3 +16,4 @@ from .brmesolve import * from .krylovsolve import * from .parallel import * +import qutip.solver.sode diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 4b78e095fd..74588f9d0d 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -225,7 +225,7 @@ def reset(self, hard=False): def _prob_func(self, state): if self.issuper: - return _data.trace_oper_ket(state) + return _data.trace_oper_ket(state).real() return _data.norm.l2(state)**2 def _norm_func(self, state): diff --git a/qutip/solver/sode/__init__.py b/qutip/solver/sode/__init__.py index e69de29bb2..62d97d11f0 100644 --- a/qutip/solver/sode/__init__.py +++ b/qutip/solver/sode/__init__.py @@ -0,0 +1,2 @@ +from .sode import * +from .ssystem import * diff --git a/qutip/solver/sode/_sode.pyx b/qutip/solver/sode/_sode.pyx index 34665961ec..ffdb8bdbe8 100644 --- a/qutip/solver/sode/_sode.pyx +++ b/qutip/solver/sode/_sode.pyx @@ -21,11 +21,10 @@ cdef class Euler: double[:, :, ::1] dW, int ntraj ): cdef int i - cdef Dense out + cdef Data out for i in range(ntraj): - out = self.step(t, state, dt, dW[i, :, :]) - state = out - return out + state = self.step(t, state, dt, dW[i, :, :]) + return state @cython.boundscheck(False) @cython.wraparound(False) @@ -40,9 +39,9 @@ cdef class Euler: cdef int i cdef _StochasticSystem system = self.system - a = system.drift(t, state) + cdef Data a = system.drift(t, state) b = system.diffusion(t, state) - new_state = _data.add(state, a, dt) + cdef Data new_state = _data.add(state, a, dt) for i in range(system.num_collapse): new_state = _data.add(new_state, b[i], dW[i, 0]) return new_state diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 088af4c1d3..ae9df83ebf 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -92,7 +92,7 @@ class _Explicit_Simple_Integrator(SIntegrator): """ integrator_options = { "dt": 0.001, - "tol": 1e-7, + "tol": 1e-10, } stepper = None N_dw = 0 @@ -101,9 +101,6 @@ def __init__(self, system, options): self.system = system self._options = self.integrator_options.copy() self.options = options - self.dt = self.options["dt"] - self.tol = self.options["tol"] - self.N_drift = system.num_collapse self.step_func = self.stepper(self.system).run def set_state(self, t, state0, generator): @@ -116,25 +113,25 @@ def integrate(self, t, copy=True): if delta_t < 0: raise ValueError("Stochastic integration time") elif delta_t == 0: - return self.t, self.state, np.zeros((0, self.N_drift, self.N_dw)) + return self.t, self.state, np.zeros(self.N_dw) - dt = self.dt + dt = self.options["dt"] N, extra = np.divmod(delta_t, dt) N = int(N) - if extra > self.tol: + if extra > self.options["tol"]: # Not a whole number of steps. N += 1 dt = delta_t / N dW = self.generator.normal( 0, np.sqrt(dt), - size=(N, self.N_drift, self.N_dw) + size=(N, self.N_dw, self.N_dw) ) self.state = self.step_func(self.t, self.state, dt, dW, N) self.t += dt * N - return self.t, self.state, np.sum(dW, axis=0) / (N * dt) + return self.t, self.state, np.sum(dW[:, :, 0], axis=0) def get_state(self, copy=True): return self.t, self.state, self.generator @@ -239,9 +236,23 @@ class PredCorr_SODE(_Explicit_Simple_Integrator): (:math:`\\alpha=1/2`, :math:`\\eta=1/2`): ``'pc-euler-imp'``, ``'pc-euler-2'`` or ``'pred-corr-2'`` """ + integrator_options = { + "dt": 0.001, + "tol": 1e-10, + "alpha": 0.0, + "eta": 0.5, + } stepper = _sode.PredCorr N_dw = 1 + def __init__(self, system, options): + self.system = system + self._options = self.integrator_options.copy() + self.options = options + self.step_func = self.stepper( + self.system, self.options["alpha"], self.options["eta"] + ).run + StochasticSolver.add_integrator(EulerSODE, "euler") StochasticSolver.add_integrator(EulerSODE, "euler-maruyama") diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index 4a7b29502a..bac884ec6d 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -11,7 +11,7 @@ import numpy as np from qutip.core import spre, spost, liouvillian __all__ = [ - "GeneralStochasticSystem", "StochasticOpenSystem", "StochasticClosedSystem" + "StochasticOpenSystem", "StochasticClosedSystem" ] @cython.boundscheck(False) @@ -81,21 +81,6 @@ cdef class _StochasticSystem: raise NotImplementedError -cdef class GeneralStochasticSystem(_StochasticSystem): - cdef object d1, d2 - - def __init__(self, a, b): - self.d1 = a - self.d2 = b - self.num_collapse = 1 - - cpdef Data drift(self, t, Data state): - return self.d1(t, state) - - cpdef list diffusion(self, t, Data state): - return self.d2(t, state) - - cdef class StochasticClosedSystem(_StochasticSystem): cdef QobjEvo H cdef list c_ops diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 1c7fba6eb6..5e798891f9 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -8,29 +8,47 @@ class StochasticTrajResult(Result): - def _post_init(self, m_ops=()): + def _post_init(self, m_ops=(), dw_factor=()): super()._post_init() self.noise = [] self.m_ops = m_ops + self.dW_factor = dw_factor self.measurements = [[] for _ in range(len(m_ops))] def _add_measurement(self, t, state, noise): expects = [m_op.expect(t, state) for m_op in self.m_ops] noises = np.sum(noise, axis=0) - for measure, expect, dW in zip(self.measurements, expects, noises): - measure.append(expect + dW) + print(self.measurements, expects, noises, self.dW_factor) + for measure, expect, dW, factor in zip( + self.measurements, expects, noises, self.dW_factor + ): + measure.append(expect + dW * factor) def add(self, t, state, noise): super().add(t, state) + if noise is not None: - self._add_measurement(t, state, noise) - self.noise.append(noise) + self.noise.append(noise) + if self.options["store_measurement"]: + dt = self.times[-1] - self.times[-2] + self._add_measurement(t, state, noise / dt) class StochasticResult(MultiTrajResult): - @property - def measurement(self): - return [] + def _reduce_expect(self, trajectory): + # Since measurements of each trajectories is kept, we keep only the + # array to save memory. + trajectory.measurements = np.array(trajectory.measurements) + if self.options["heterodyne"]: + shape = trajectory.measurements.shape + trajectory.measurements.reshape(-1, 2, shape[-1]) + self.measurement.append(trajectory.measurements) + + def _post_init(self): + super()._post_init() + self.measurement = [] + if self.options['store_measurement']: + self.add_processor(self._reduce_measurements) def smesolve(H, rho0, tlist, c_ops=(), sc_ops=(), e_ops=(), m_ops=(), @@ -93,7 +111,6 @@ def smesolve(H, rho0, tlist, c_ops=(), sc_ops=(), e_ops=(), m_ops=(), return sol.run(rho0, tlist, ntraj, e_ops=e_ops) - def ssesolve(H, psi0, tlist, sc_ops=(), e_ops=(), m_ops=(), args={}, ntraj=500, options=None): """ @@ -153,6 +170,7 @@ class StochasticSolver(MultiTrajSolver): name = "StochasticSolver" resultclass = StochasticResult _avail_integrators = {} + system = None solver_options = { "progress_bar": "text", "progress_kwargs": {"chunk_size": 10}, @@ -166,45 +184,64 @@ class StochasticSolver(MultiTrajSolver): "num_cpus": None, "bitgenerator": None, "heterodyne": False, + "store_measurement": False, + "dw_factor": None, } - def __init__(self, H, sc_ops, heterodyne=False, *, options=None, m_ops=()): - self._options = self.solver_options.copy() + def __init__(self, H, sc_ops, *, options=None, m_ops=()): self.options = options if not isinstance(H, (Qobj, QobjEvo)): raise TypeError("...") H = QobjEvo(H) - if isinstance(sc_ops, (Qobj, QobjEvo)): sc_ops = [sc_ops] sc_ops = [QobjEvo(c_op) for c_op in sc_ops] + if any(not c_op.isoper for c_op in sc_ops): raise TypeError("sc_ops must be operators") - if H.issuper: - rhs = StochasticOpenSystem(H, sc_ops, heterodyne) - else: - rhs = StochasticClosedSystem(H, sc_ops, heterodyne) - + rhs = self._prep_system(H, sc_ops, self.options["heterodyne"]) super().__init__(rhs, options=options) - if len(m_ops) == rhs.num_collapse: - self.m_ops = m_ops - elif heterodyne: - self.m_ops = [] - for sc_op in sc_ops: - self.m_ops += [ - sc_op + sc_op.dag(), -1j * (sc_op - sc_op.dag()) - ] + if self.options["store_measurement"]: + n_m_ops = len(sc_ops) * (1 + int(self.options["heterodyne"])) + dW_factor = self.options["dW_factor"] + + if len(m_ops) == n_m_ops: + self.m_ops = m_ops + elif self.options["heterodyne"]: + self.m_ops = [] + for op in sc_ops: + self.m_ops += [ + op + op.dag(), -1j * (op - op.dag()) + ] + dW_factor = dW_factor or 2**0.5 + else: + self.m_ops = [op + op.dag() for op in sc_ops] + dW_factor = dW_factor or 1. + + if not isinstance(dW_factor, Iterable): + dW_factor = [dW_factor] * n_m_ops + + if len(dW_factor) == len(sc_ops) and self.options["heterodyne"]: + dW_factor = [ i for i in dW_factor for _ in range(2) ] + + if len(dW_factor) != n_m_ops: + raise ValueError("Bad dW_factor option") + self.dW_factors = dW_factor + else: - self.m_ops = [sc_op + sc_op.dag() for sc_op in sc_ops] + self.m_ops = [] + self.dW_factors = [] def _run_one_traj(self, seed, state, tlist, e_ops): """ Run one trajectory and return the result. """ - result = StochasticTrajResult(e_ops, self.options, m_ops=self.m_ops) + result = StochasticTrajResult( + e_ops, self.options, m_ops=self.m_ops, dw_factor=self.dW_factors, + ) generator = self._get_generator(seed) self._integrator.set_state(tlist[0], state, generator) state_t = self._restore_state(state, copy=False) @@ -229,7 +266,13 @@ class SMESolver(StochasticSolver): name = "smesolve" _avail_integrators = {} + def _prep_system(self, L, sc_ops, heterodyne): + if not L.issuper: + L = liouvillian(L) + return StochasticOpenSystem(L, sc_ops, heterodyne) + class SSESolver(StochasticSolver): name = "ssesolve" _avail_integrators = {} + _prep_system = StochasticClosedSystem From 0a61ec52fddcb63f7b525ccd3fac0fb8ef72c337 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Thu, 16 Feb 2023 12:14:41 +0100 Subject: [PATCH 081/602] Switch bosonic and fermionic test tolerances to relative to better test small values. --- qutip/tests/solver/heom/test_bofin_solvers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutip/tests/solver/heom/test_bofin_solvers.py b/qutip/tests/solver/heom/test_bofin_solvers.py index c168070da9..72b737a91c 100644 --- a/qutip/tests/solver/heom/test_bofin_solvers.py +++ b/qutip/tests/solver/heom/test_bofin_solvers.py @@ -1022,9 +1022,9 @@ def test_discrete_level_model_fermionic_bath_with_coupled_bosonic_bath( ] sys_occupation = dlm.sys_occupation(result.states) - np.testing.assert_allclose(current_ref, current, atol=1e-3) + np.testing.assert_allclose(current_ref, current, rtol=1e-3) np.testing.assert_allclose( - sys_occupation_ref, sys_occupation, atol=1e-3, + sys_occupation_ref, sys_occupation, rtol=1e-3, ) if evo != "qobjevo_timedep": From 10b7aa95c5f95dd1d4f146b226e300b9a61429fc Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 16 Feb 2023 17:06:24 -0500 Subject: [PATCH 082/602] Passing test for smesolve --- qutip/solver/mcsolve.py | 4 +- qutip/solver/sode/sode.py | 6 +- qutip/solver/sode/ssystem.pyx | 23 +++-- qutip/solver/stochastic.py | 38 ++++++--- qutip/tests/solver/test_stochastic.py | 88 ++++++++++++++++++++ qutip/tests/solver/test_stochastic_system.py | 23 ++--- 6 files changed, 146 insertions(+), 36 deletions(-) create mode 100644 qutip/tests/solver/test_stochastic.py diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 74588f9d0d..ed7adae017 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -225,12 +225,12 @@ def reset(self, hard=False): def _prob_func(self, state): if self.issuper: - return _data.trace_oper_ket(state).real() + return _data.trace_oper_ket(state).real return _data.norm.l2(state)**2 def _norm_func(self, state): if self.issuper: - return _data.trace_oper_ket(state) + return _data.trace_oper_ket(state).real return _data.norm.l2(state) def _find_collapse_time(self, norm_old, norm, t_prev, t_final): diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index ae9df83ebf..8e498d1630 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -97,8 +97,8 @@ class _Explicit_Simple_Integrator(SIntegrator): stepper = None N_dw = 0 - def __init__(self, system, options): - self.system = system + def __init__(self, rhs, options): + self.system = rhs self._options = self.integrator_options.copy() self.options = options self.step_func = self.stepper(self.system).run @@ -125,7 +125,7 @@ def integrate(self, t, copy=True): dW = self.generator.normal( 0, np.sqrt(dt), - size=(N, self.N_dw, self.N_dw) + size=(N, self.system.num_collapse, self.N_dw) ) self.state = self.step_func(self.t, self.state, dt, dW, N) diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index bac884ec6d..7250c8779b 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -86,18 +86,18 @@ cdef class StochasticClosedSystem(_StochasticSystem): cdef list c_ops cdef list cpcd_ops - def __init__(self, H, c_ops, heterodyne): + def __init__(self, H, sc_ops, heterodyne=False): self.H = -1j * H if heterodyne: self.c_ops = [] - for c_op in c_ops: + for c_op in sc_ops: self.c_ops.append(c_op / np.sqrt(2)) self.c_ops.append(c_op * (-1j / np.sqrt(2))) self.cpcd_ops.append((c_op + c_op.dag()) / np.sqrt(2)) self.cpcd_ops.append((-c_op + c_op.dag()) * 1j / np.sqrt(2)) else: - self.c_ops = c_ops - self.cpcd_ops = [op + op.dag() for op in c_ops] + self.c_ops = sc_ops + self.cpcd_ops = [op + op.dag() for op in sc_ops] self.num_collapse = len(self.c_ops) for c_op in self.c_ops: @@ -146,17 +146,24 @@ cdef class StochasticOpenSystem(_StochasticSystem): cdef readonly complex[:, :, ::1] _Lb cdef readonly complex[:, :, :, ::1] _LLb - def __init__(self, H, c_ops, heterodyne): - self.L = H + liouvillian(None, c_ops) + def __init__(self, H, sc_ops, c_ops=(), heterodyne=False): + print(H, sc_ops, c_ops, False) + if H.issuper: + self.L = H + liouvillian(None, sc_ops) + else: + self.L = liouvillian(H, sc_ops) + if c_ops: + self.L = self.L + liouvillian(None, c_ops) + if heterodyne: self.c_ops = [] - for c in c_ops: + for c in sc_ops: self.c_ops += [ (spre(c) + spost(c.dag())) / np.sqrt(2), (spre(c) - spost(c.dag())) * -1j / np.sqrt(2) ] else: - self.c_ops = [spre(op) + spost(op.dag()) for op in c_ops] + self.c_ops = [spre(op) + spost(op.dag()) for op in sc_ops] self.num_collapse = len(self.c_ops) self.issuper = True self.dims = self.L.dims diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 5e798891f9..cef66af232 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -5,6 +5,7 @@ from .multitraj import MultiTrajSolver from ..import Qobj, QobjEvo, liouvillian, lindblad_dissipator import numpy as np +from collections.abc import Iterable class StochasticTrajResult(Result): @@ -17,10 +18,8 @@ def _post_init(self, m_ops=(), dw_factor=()): def _add_measurement(self, t, state, noise): expects = [m_op.expect(t, state) for m_op in self.m_ops] - noises = np.sum(noise, axis=0) - print(self.measurements, expects, noises, self.dW_factor) for measure, expect, dW, factor in zip( - self.measurements, expects, noises, self.dW_factor + self.measurements, expects, noise, self.dW_factor ): measure.append(expect + dW * factor) @@ -35,13 +34,15 @@ def add(self, t, state, noise): class StochasticResult(MultiTrajResult): - def _reduce_expect(self, trajectory): + def _reduce_measurements(self, trajectory): # Since measurements of each trajectories is kept, we keep only the # array to save memory. trajectory.measurements = np.array(trajectory.measurements) if self.options["heterodyne"]: shape = trajectory.measurements.shape - trajectory.measurements.reshape(-1, 2, shape[-1]) + trajectory.measurements = ( + trajectory.measurements.reshape(-1, 2, shape[-1]) + ) self.measurement.append(trajectory.measurements) def _post_init(self): @@ -188,25 +189,30 @@ class StochasticSolver(MultiTrajSolver): "dw_factor": None, } - def __init__(self, H, sc_ops, *, options=None, m_ops=()): + def __init__(self, H, sc_ops, *, c_ops=(), options=None, m_ops=()): self.options = options if not isinstance(H, (Qobj, QobjEvo)): raise TypeError("...") H = QobjEvo(H) + if isinstance(sc_ops, (Qobj, QobjEvo)): sc_ops = [sc_ops] sc_ops = [QobjEvo(c_op) for c_op in sc_ops] + if isinstance(c_ops, (Qobj, QobjEvo)): + c_ops = [c_ops] + c_ops = [QobjEvo(c_op) for c_op in c_ops] + if any(not c_op.isoper for c_op in sc_ops): raise TypeError("sc_ops must be operators") - rhs = self._prep_system(H, sc_ops, self.options["heterodyne"]) + rhs = self._prep_system(H, sc_ops, c_ops) super().__init__(rhs, options=options) if self.options["store_measurement"]: n_m_ops = len(sc_ops) * (1 + int(self.options["heterodyne"])) - dW_factor = self.options["dW_factor"] + dW_factor = self.options["dw_factor"] if len(m_ops) == n_m_ops: self.m_ops = m_ops @@ -266,13 +272,19 @@ class SMESolver(StochasticSolver): name = "smesolve" _avail_integrators = {} - def _prep_system(self, L, sc_ops, heterodyne): - if not L.issuper: - L = liouvillian(L) - return StochasticOpenSystem(L, sc_ops, heterodyne) + def _prep_system(self, H, sc_ops, c_ops): + return StochasticOpenSystem( + H, sc_ops, c_ops, self.options["heterodyne"] + ) class SSESolver(StochasticSolver): name = "ssesolve" _avail_integrators = {} - _prep_system = StochasticClosedSystem + + def _prep_system(self, H, sc_ops, c_ops): + if c_ops: + raise ValueError("ssesolve c_ops") + return StochasticClosedSystem( + H, sc_ops, self.options["heterodyne"] + ) diff --git a/qutip/tests/solver/test_stochastic.py b/qutip/tests/solver/test_stochastic.py new file mode 100644 index 0000000000..a87259d38b --- /dev/null +++ b/qutip/tests/solver/test_stochastic.py @@ -0,0 +1,88 @@ +import pytest +import numpy as np +from qutip import ( + mesolve, liouvillian, QobjEvo, spre, spost, + destroy, coherent, qeye, fock_dm, num +) +from qutip.solver.stochastic import smesolve +from qutip.core import data as _data + + +def f(t, a): + return a * t + +def _make_system(N, system): + gamma = 0.25 + a = destroy(N) + + if system == "simple": + H = [a.dag() * a] + sc_ops = [np.sqrt(gamma) * a] + + elif system == "2 c_ops": + H = [a.dag() * a] + sc_ops = [np.sqrt(gamma) * a, gamma * a * a] + + elif system == "H td": + H = [[a.dag() * a, f]] + sc_ops = [np.sqrt(gamma) * a] + + elif system == "complex": + H = [a.dag() * a + a.dag() + a] + sc_ops = [np.sqrt(gamma) * a, gamma * a * a] + + elif system == "c_ops td": + H = [a.dag() * a] + sc_ops = [[np.sqrt(gamma) * a, f]] + + return H, sc_ops + + +@pytest.mark.parametrize("system", [ + "simple", "2 c_ops", "H td", "complex", "c_ops td", +]) +@pytest.mark.parametrize("heterodyne", [True, False]) +def test_smesolve(heterodyne, system): + "Stochastic: smesolve: homodyne, time-dependent H" + tol = 0.05 + + N = 4 + ntraj = 20 + + H, sc_ops = _make_system(N, system) + psi0 = coherent(N, 0.5) + a = destroy(N) + e_ops = [a.dag() * a, a + a.dag(), (-1j)*(a - a.dag())] + + times = np.linspace(0, 1.0, 21) + res_ref = mesolve(H, psi0, times, sc_ops, e_ops, args={"a": 2}) + + options = { + "heterodyne": heterodyne, + "store_measurement": True, + "map": "serial", + "method": "milstein", + } + + res = smesolve( + H, psi0, times, [], sc_ops, e_ops, + ntraj=ntraj, args={"a": 2}, options=options + ) + + for idx in range(len(e_ops)): + np.testing.assert_allclose( + res.expect[idx], res_ref.expect[idx], rtol=tol, atol=tol + ) + + assert len(res.measurement) == ntraj + + if heterodyne: + assert all([ + m.shape == (len(sc_ops), 2, len(times)-1) + for m in res.measurement + ]) + else: + assert all([ + m.shape == (len(sc_ops), len(times)-1) + for m in res.measurement + ]) diff --git a/qutip/tests/solver/test_stochastic_system.py b/qutip/tests/solver/test_stochastic_system.py index 455e2fd35d..2cdfd9d5b6 100644 --- a/qutip/tests/solver/test_stochastic_system.py +++ b/qutip/tests/solver/test_stochastic_system.py @@ -4,6 +4,7 @@ basis, rand_herm, fock_dm, liouvillian, operator_to_vector ) from qutip.solver.sode.ssystem import * +from qutip.solver.sode.ssystem import SimpleStochasticSystem import qutip.core.data as _data import pytest from itertools import product @@ -92,16 +93,17 @@ def _check_equivalence(f, target, args): """ Check that the error is proportional to `dt`. """ - dts = np.logspace(-5, -1, 9) + dts = np.logspace(-4, -1, 7) errors_dt = np.array([ _data.norm.l2(f(*args, dt=dt) - target) for dt in dts ]) - poly = np.polyfit(np.log(dts), np.log(errors_dt+1e-16), 1)[0] - if poly > 1.05: - print(poly, np.log(errors_dt)) - print(dts, errors_dt) - return poly < 1.05 + if np.all(errors_dt < 1e-6): + return True + + power = np.polyfit(np.log(dts), np.log(errors_dt + 1e-16), 1)[0] + # Sometime the dt term is cancelled and the dt**2 term is dominant + return power > 0.9 def _run_derr_check(solver, state): @@ -128,6 +130,7 @@ def _run_derr_check(solver, state): ) for k in range(N): + print(i, j, k) assert _check_equivalence( LL(solver, k, j, b), solver.LiLjbk(k, j, i), (t, state) ) @@ -143,7 +146,7 @@ def _make_oper(kind, N): elif kind == "tridiag": out = destroy(N) + num(N) + create(N) elif kind == "td": - out = [num(N), [destroy(N) + create(N), lambda t: t]] + out = [num(N), [destroy(N) + create(N), lambda t: 1 + t]] elif kind == "rand": out = rand_herm(N) return QobjEvo(out) @@ -157,17 +160,17 @@ def _make_oper(kind, N): pytest.param("qeye", ["td"], id='c_ops td'), pytest.param("rand", ["rand"], id='random'), ]) -@pytest.mark.parametrize('type', ["sme"]) # "sse" +@pytest.mark.parametrize('type', ["sme", "sse"]) @pytest.mark.parametrize('heterodyne', [False, True]) def test_system(H, c_ops, type, heterodyne): N = 5 H = _make_oper(H, N) c_ops = [_make_oper(op, N) for op in c_ops] if type == "sse": - system = StochasticClosedSystem(H, c_ops, heterodyne) + system = SimpleStochasticSystem(H, c_ops) state = basis(N, N-2).data else: H = liouvillian(H) - system = StochasticOpenSystem(H, c_ops, heterodyne) + system = StochasticOpenSystem(H, c_ops, heterodyne=heterodyne) state = operator_to_vector(fock_dm(N, N-2)).data _run_derr_check(system, state) From e9f494afc6e8ef6764555922dbea6408368b20bf Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 17 Feb 2023 18:07:39 -0500 Subject: [PATCH 083/602] Add rouchon --- qutip/solve/stochastic.py | 14 +++- qutip/solver/sode/__init__.py | 1 + qutip/solver/sode/{ito.py => noise.py} | 15 ++++- qutip/solver/sode/rouchon.py | 46 +++++++++++++ qutip/solver/sode/sode.py | 92 ++++++++------------------ qutip/solver/sode/taylorapprox.py | 67 +++++++++++++++++++ qutip/solver/stochastic.py | 78 +++++++++++++--------- qutip/tests/solver/test_sode_method.py | 4 +- 8 files changed, 217 insertions(+), 100 deletions(-) rename qutip/solver/sode/{ito.py => noise.py} (81%) create mode 100644 qutip/solver/sode/rouchon.py create mode 100644 qutip/solver/sode/taylorapprox.py diff --git a/qutip/solve/stochastic.py b/qutip/solve/stochastic.py index cea188bd60..bc9d50e993 100644 --- a/qutip/solve/stochastic.py +++ b/qutip/solve/stochastic.py @@ -579,8 +579,10 @@ def smesolve(H, rho0, times, c_ops=[], sc_ops=[], e_ops=[], if _safe_mode: _safety_checks(sso) + print(sso.solver_code) + if sso.solver_code == 120: - return _positive_map(sso, e_ops_dict) + return _positive_map(sso, e_ops_dict, _dry_run) sso.LH = liouvillian(sso.H, c_ops=sso.sc_ops + sso.c_ops) * sso.dt if sso.method == 'homodyne' or sso.method is None: @@ -767,7 +769,7 @@ def ssesolve(H, psi0, times, sc_ops=[], e_ops=[], return res -def _positive_map(sso, e_ops_dict): +def _positive_map(sso, e_ops_dict, _dry_run=False): if sso.method == 'homodyne' or sso.method is None: sops = sso.sc_ops if sso.m_ops is None: @@ -811,7 +813,8 @@ def _prespostdag(op): for op in sso.c_ops: LH += op.dag() @ op * (-sso.dt * 0.5) - sso.pp += spre(op) * spost(op.dag()) * sso.dt + opop = spre(op) * spost(op.dag()) + sso.pp += opop * sso.dt for i, op in enumerate(sops): LH += (-sso.dt * 0.5) * op.dag() @ op @@ -829,6 +832,11 @@ def _prespostdag(op): sso.solver_obj = PmSMESolver sso.solver_name = "smesolve_" + sso.solver + + if _dry_run: + ssolver = sso.solver_obj() + ssolver.set_solver(sso) + return ssolver res = _sesolve_generic(sso, sso.options, sso.progress_bar) if e_ops_dict: diff --git a/qutip/solver/sode/__init__.py b/qutip/solver/sode/__init__.py index 62d97d11f0..ac3863eabd 100644 --- a/qutip/solver/sode/__init__.py +++ b/qutip/solver/sode/__init__.py @@ -1,2 +1,3 @@ from .sode import * from .ssystem import * +from .taylorapprox import * diff --git a/qutip/solver/sode/ito.py b/qutip/solver/sode/noise.py similarity index 81% rename from qutip/solver/sode/ito.py rename to qutip/solver/sode/noise.py index d3ea5df255..b02681970d 100644 --- a/qutip/solver/sode/ito.py +++ b/qutip/solver/sode/noise.py @@ -1,7 +1,11 @@ import numpy as np +__all__ = [] -class MultiNoise: +class _Noise: + """ + + """ def __init__(self, T, dt, num=1): N = int(np.round(T / dt)) self.T = T @@ -10,10 +14,16 @@ def __init__(self, T, dt, num=1): self.noise = np.random.randn(N, num) * dt**0.5 def dw(self, dt): + """ + Ito integral I(i). + """ N = int(np.round(dt /self.dt)) return self.noise.reshape(-1, N, self.num).sum(axis=1) def dz(self, dt): + """ + Ito integral I(0, i). + """ N = int(np.round(dt /self.dt)) return np.einsum( "ijk,j->ik", @@ -22,6 +32,9 @@ def dz(self, dt): ) * self.dt def dW(self, dt): + """ + Noise used for Ito-Taylor integrators of order up to 1.5. + """ N = int(np.round(dt / self.dt)) noise = self.noise.copy() if noise.shape[0] % N: diff --git a/qutip/solver/sode/rouchon.py b/qutip/solver/sode/rouchon.py new file mode 100644 index 0000000000..2bd4e89c03 --- /dev/null +++ b/qutip/solver/sode/rouchon.py @@ -0,0 +1,46 @@ + + +import qutip as qt +import numpy as np +from qutip.core import data as _data + +class Rouchon: + def __init__(self, rhs, options): + self.H = rhs.H + if self.H.issuper: + raise TypeError("...") + dtype = type(self.H(0).data) + self.c_ops = rhs.c_ops + self.sc_ops = rhs.sc_ops + self.cpcds = [op + op.dag() for op in self.sc_ops] + self.M = ( + - 1j * self.H + - sum(op.dag() @ op for op in self.c_ops) * 0.5 + - sum(op.dag() @ op for op in self.sc_ops) * 0.5 + ) + self.num_collapses = len(self.sc_ops) + self.scc = [ + [self.sc_ops[i] @ self.sc_ops[j] for i in range(j+1)] + for j in range(self.num_collapses) + ] + + self.id = _data.identity[dtype](self.H.shape[0]) + + def step(self, t, state, dt, dW): + # Same output as old rouchon up to nuerical error 1e-16 + # But 7x slower + dy = [ + op.expect_data(t, state) * dt + dw + for op, dw in zip(self.cpcds, dW[:, 0]) + ] + M = _data.add(self.id, self.M._call(t), dt) + for i in range(self.num_collapses): + M = _data.add(M, self.sc_ops[i]._call(t), dy[i]) + M = _data.add(M, self.scc[i][i]._call(t), (dy[i]**2-dt)/2) + for j in range(i): + M = _data.add(M, self.scc[i][j]._call(t), dy[i]*dy[j]) + out = M @ state @ M.adjoint() + for cop in self.c_ops: + op = cop._call(t) + out += op @ state @ op.adjoint() * dt + return out / _data.trace(out) diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 8e498d1630..1201ddae8a 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -1,7 +1,11 @@ import numpy as np from . import _sode from ..integrator.integrator import Integrator -from ..stochastic import StochasticSolver +from ..stochastic import StochasticSolver, SMESolver +from .rouchon import Rouchon + + +__all__ = ["SIntegrator", "PlatenSODE", "PredCorr_SODE"] class SIntegrator(Integrator): @@ -98,7 +102,7 @@ class _Explicit_Simple_Integrator(SIntegrator): N_dw = 0 def __init__(self, rhs, options): - self.system = rhs + self.system = rhs() self._options = self.integrator_options.copy() self.options = options self.step_func = self.stepper(self.system).run @@ -154,18 +158,6 @@ def options(self, new_options): Integrator.options.fset(self, new_options) -class EulerSODE(_Explicit_Simple_Integrator): - """ - A simple generalization of the Euler method for ordinary - differential equations to stochastic differential equations. Only - solver which could take non-commuting ``sc_ops``. - - - Order: 0.5 - """ - stepper = _sode.Euler - N_dw = 1 - - class PlatenSODE(_Explicit_Simple_Integrator): """ Explicit scheme, creates the Milstein using finite differences @@ -180,47 +172,6 @@ class PlatenSODE(_Explicit_Simple_Integrator): N_dw = 1 -class Explicit1_5_SODE(_Explicit_Simple_Integrator): - """ - Explicit order 1.5 strong schemes. Reproduce the order 1.5 strong - Taylor scheme using finite difference instead of derivatives. - Slower than ``taylor15`` but usable when derrivatives cannot be - analytically obtained. - See eq. (2.13) of chapter 11.2 of Peter E. Kloeden and Exkhard Platen, - *Numerical Solution of Stochastic Differential Equations.* - - - Order: strong 1.5 - """ - stepper = _sode.Explicit15 - N_dw = 2 - - -class Taylor1_5_SODE(_Explicit_Simple_Integrator): - """ - Order 1.5 strong Taylor scheme. Solver with more terms of the - Ito-Taylor expansion. Default solver for :obj:`~smesolve` and - :obj:`~ssesolve`. See eq. (4.6) of chapter 10.4 of Peter E. Kloeden and - Exkhard Platen, *Numerical Solution of Stochastic Differential Equations*. - - - Order strong 1.5 - """ - stepper = _sode.Taylor15 - N_dw = 2 - - -class Milstein_SODE(_Explicit_Simple_Integrator): - """ - An order 1.0 strong Taylor scheme. Better approximate numerical - solution to stochastic differential equations. See eq. (2.9) of - chapter 12.2 of Peter E. Kloeden and Exkhard Platen, - *Numerical Solution of Stochastic Differential Equations*.. - - - Order strong 1.0 - """ - stepper = _sode.Milstein - N_dw = 1 - - class PredCorr_SODE(_Explicit_Simple_Integrator): """ Generalization of the trapezoidal method to stochastic differential @@ -245,8 +196,8 @@ class PredCorr_SODE(_Explicit_Simple_Integrator): stepper = _sode.PredCorr N_dw = 1 - def __init__(self, system, options): - self.system = system + def __init__(self, rhs, options): + self.system = rhs() self._options = self.integrator_options.copy() self.options = options self.step_func = self.stepper( @@ -254,10 +205,25 @@ def __init__(self, system, options): ).run -StochasticSolver.add_integrator(EulerSODE, "euler") -StochasticSolver.add_integrator(EulerSODE, "euler-maruyama") +class RouchonSODE(SIntegrator): + """ + Scheme keeping the positivity of the density matrix + (:obj:`~smesolve` only). + See eq. (4) Pierre Rouchon and Jason F. Ralpha, + *Efficient Quantum Filtering for Quantum Feedback Control*, + `arXiv:1410.5345 [quant-ph] `_, + Phys. Rev. A 91, 012118, (2015). + + - Order: strong 1 + """ + stepper = _sode.Platen + N_dw = 1 + + def __init__(self, rhs, options): + self._options = self.integrator_options.copy() + self.options = options + self.step_func = Rouchon(rhs) + + StochasticSolver.add_integrator(PlatenSODE, "platen") -StochasticSolver.add_integrator(Explicit1_5_SODE, "explicit1.5") -StochasticSolver.add_integrator(Taylor1_5_SODE, "taylor15") -StochasticSolver.add_integrator(PredCorr_SODE, "pred_corr") -StochasticSolver.add_integrator(Milstein_SODE, "milstein") +SMESolver.add_integrator(PredCorr_SODE, "pred_corr") diff --git a/qutip/solver/sode/taylorapprox.py b/qutip/solver/sode/taylorapprox.py new file mode 100644 index 0000000000..3c0c36edf5 --- /dev/null +++ b/qutip/solver/sode/taylorapprox.py @@ -0,0 +1,67 @@ +import numpy as np +from . import _sode +from .sode import _Explicit_Simple_Integrator +from ..stochastic import StochasticSolver, SMESolver + + +__all__ = ["EulerSODE", "Milstein_SODE", "Taylor1_5_SODE", "Explicit1_5_SODE"] + + +class EulerSODE(_Explicit_Simple_Integrator): + """ + A simple generalization of the Euler method for ordinary + differential equations to stochastic differential equations. Only + solver which could take non-commuting ``sc_ops``. + + - Order: 0.5 + """ + stepper = _sode.Euler + N_dw = 1 + + +class Milstein_SODE(_Explicit_Simple_Integrator): + """ + An order 1.0 strong Taylor scheme. Better approximate numerical + solution to stochastic differential equations. See eq. (2.9) of + chapter 12.2 of Peter E. Kloeden and Exkhard Platen, + *Numerical Solution of Stochastic Differential Equations*.. + + - Order strong 1.0 + """ + stepper = _sode.Milstein + N_dw = 1 + + +class Taylor1_5_SODE(_Explicit_Simple_Integrator): + """ + Order 1.5 strong Taylor scheme. Solver with more terms of the + Ito-Taylor expansion. Default solver for :obj:`~smesolve` and + :obj:`~ssesolve`. See eq. (4.6) of chapter 10.4 of Peter E. Kloeden and + Exkhard Platen, *Numerical Solution of Stochastic Differential Equations*. + + - Order strong 1.5 + """ + stepper = _sode.Taylor15 + N_dw = 2 + + +class Explicit1_5_SODE(_Explicit_Simple_Integrator): + """ + Explicit order 1.5 strong schemes. Reproduce the order 1.5 strong + Taylor scheme using finite difference instead of derivatives. + Slower than ``taylor15`` but usable when derrivatives cannot be + analytically obtained. + See eq. (2.13) of chapter 11.2 of Peter E. Kloeden and Exkhard Platen, + *Numerical Solution of Stochastic Differential Equations.* + + - Order: strong 1.5 + """ + stepper = _sode.Explicit15 + N_dw = 2 + + +StochasticSolver.add_integrator(EulerSODE, "euler") +StochasticSolver.add_integrator(EulerSODE, "euler-maruyama") +StochasticSolver.add_integrator(Explicit1_5_SODE, "explicit1.5") +SMESolver.add_integrator(Taylor1_5_SODE, "taylor15") +SMESolver.add_integrator(Milstein_SODE, "milstein") diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index cef66af232..97f654b3c0 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -52,6 +52,46 @@ def _post_init(self): self.add_processor(self._reduce_measurements) +class StochasticRHS: + def __init__(self, issuper, H, sc_ops, c_ops, heterodyne): + + if not isinstance(H, (Qobj, QobjEvo)): + raise TypeError("...") + self.H = QobjEvo(H) + + if isinstance(sc_ops, (Qobj, QobjEvo)): + sc_ops = [sc_ops] + self.sc_ops = [QobjEvo(c_op) for c_op in sc_ops] + + if isinstance(c_ops, (Qobj, QobjEvo)): + c_ops = [c_ops] + self.c_ops = [QobjEvo(c_op) for c_op in c_ops] + + if any(not c_op.isoper for c_op in c_ops): + raise TypeError("c_ops must be operators") + + if any(not c_op.isoper for c_op in sc_ops): + raise TypeError("sc_ops must be operators") + + self.issuper = issuper + self.heterodyne = heterodyne + + if self.issuper and not self.H.issuper: + self.dims = [self.H.dims, self.H.dims] + else: + self.dims = self.H.dims + + def __call__(self): + if self.issuper: + return StochasticOpenSystem( + self.H , self.sc_ops, self.c_ops, self.heterodyne + ) + else: + return StochasticClosedSystem( + self.H , self.sc_ops, self.heterodyne + ) + + def smesolve(H, rho0, tlist, c_ops=(), sc_ops=(), e_ops=(), m_ops=(), args={}, ntraj=500, options=None): """ @@ -191,32 +231,20 @@ class StochasticSolver(MultiTrajSolver): def __init__(self, H, sc_ops, *, c_ops=(), options=None, m_ops=()): self.options = options + heterodyne = self.options["heterodyne"] + if c_ops: + raise ValueError("ssesolve c_ops") - if not isinstance(H, (Qobj, QobjEvo)): - raise TypeError("...") - H = QobjEvo(H) - - if isinstance(sc_ops, (Qobj, QobjEvo)): - sc_ops = [sc_ops] - sc_ops = [QobjEvo(c_op) for c_op in sc_ops] - - if isinstance(c_ops, (Qobj, QobjEvo)): - c_ops = [c_ops] - c_ops = [QobjEvo(c_op) for c_op in c_ops] - - if any(not c_op.isoper for c_op in sc_ops): - raise TypeError("sc_ops must be operators") - - rhs = self._prep_system(H, sc_ops, c_ops) + rhs = StochasticRHS(self.name=="smesolve", H, sc_ops, c_ops, heterodyne) super().__init__(rhs, options=options) if self.options["store_measurement"]: - n_m_ops = len(sc_ops) * (1 + int(self.options["heterodyne"])) + n_m_ops = len(sc_ops) * (1 + int(heterodyne)) dW_factor = self.options["dw_factor"] if len(m_ops) == n_m_ops: self.m_ops = m_ops - elif self.options["heterodyne"]: + elif heterodyne: self.m_ops = [] for op in sc_ops: self.m_ops += [ @@ -230,7 +258,7 @@ def __init__(self, H, sc_ops, *, c_ops=(), options=None, m_ops=()): if not isinstance(dW_factor, Iterable): dW_factor = [dW_factor] * n_m_ops - if len(dW_factor) == len(sc_ops) and self.options["heterodyne"]: + if len(dW_factor) == len(sc_ops) and heterodyne: dW_factor = [ i for i in dW_factor for _ in range(2) ] if len(dW_factor) != n_m_ops: @@ -272,19 +300,7 @@ class SMESolver(StochasticSolver): name = "smesolve" _avail_integrators = {} - def _prep_system(self, H, sc_ops, c_ops): - return StochasticOpenSystem( - H, sc_ops, c_ops, self.options["heterodyne"] - ) - class SSESolver(StochasticSolver): name = "ssesolve" _avail_integrators = {} - - def _prep_system(self, H, sc_ops, c_ops): - if c_ops: - raise ValueError("ssesolve c_ops") - return StochasticClosedSystem( - H, sc_ops, self.options["heterodyne"] - ) diff --git a/qutip/tests/solver/test_sode_method.py b/qutip/tests/solver/test_sode_method.py index 5d3436d08a..dd057eaa79 100644 --- a/qutip/tests/solver/test_sode_method.py +++ b/qutip/tests/solver/test_sode_method.py @@ -5,7 +5,7 @@ import qutip.solver.sode._sode as _sode import pytest from qutip.solver.sode.ssystem import SimpleStochasticSystem -from qutip.solver.sode.ito import MultiNoise +from qutip.solver.sode.noise import _Noise def get_error_order(system, state, method, plot=False, **kw): @@ -18,7 +18,7 @@ def get_error_order(system, state, method, plot=False, **kw): # state = rand_ket(system.dims[0]).data err = np.zeros(len(ts), dtype=float) for _ in range(num_runs): - noise = MultiNoise(0.1, 0.000001, system.num_collapse) + noise = _Noise(0.1, 0.000001, system.num_collapse) for i, t in enumerate(ts): out = stepper.run(0, state.copy(), t, noise.dW(t), 1) target = system.analytic(t, noise.dw(t)[0]) @ state From edcdd75b50acd97eaa1bb8877b775442034d71ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Mon, 20 Feb 2023 08:59:10 -0500 Subject: [PATCH 084/602] Apply suggestions from code review Co-authored-by: Asier Galicia <57414022+AGaliciaMartinez@users.noreply.github.com> --- qutip/core/coefficient.py | 4 ++-- qutip/core/cy/qobjevo.pyx | 4 ++-- qutip/core/options.py | 9 ++++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/qutip/core/coefficient.py b/qutip/core/coefficient.py index 9aafca4dd3..2d702fd0c9 100644 --- a/qutip/core/coefficient.py +++ b/qutip/core/coefficient.py @@ -52,9 +52,9 @@ def _return(base, **kwargs): # The `coefficient` function is dispatcher for the type of the `base` to the -# function that create the `Coefficient` object. `coefficient_builders` store +# function that created the `Coefficient` object. `coefficient_builders` stores # the map `type -> function(base, **kw)`. Optional module can add their -# `Coefficient` specialization here. +# `Coefficient` specializations here. coefficient_builders = { Coefficient: _return, np.ndarray: InterCoefficient, diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index daf2f2942f..34d95edda6 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -287,7 +287,7 @@ cdef class QobjEvo: @classmethod def _restore(cls, elements, dims, shape, type, superrep, flags): - """ Recreate a QobjEvo without using __init__ """ + """Recreate a QobjEvo without using __init__. """ cdef QobjEvo out = cls.__new__(cls) out.elements = elements out.dims = dims @@ -800,7 +800,7 @@ cdef class QobjEvo: The QobjEvo as a list, element are either :class:`Qobj` for constant parts, ``[Qobj, Coefficient]`` for coefficient based term. The original format of the :class:`Coefficient` is not restored. - Lastly if the original `QobjEvo` is constructed with an function + Lastly if the original `QobjEvo` is constructed with a function returning a Qobj, the term is returned as a pair of :class:`Qobj` and args (``dict``). """ diff --git a/qutip/core/options.py b/qutip/core/options.py index a2b425dcd1..5a60ffa509 100644 --- a/qutip/core/options.py +++ b/qutip/core/options.py @@ -91,11 +91,10 @@ class CoreOptions(QutipOptions): ``pythonic`` is used. default_dtype : Nonetype, str, type {None} - When set, function creating :class:`Qobj` such as :func:"qeye" or - :func:"rand_herm" will use the specified data type. Any data-layer - known to `qutip.data.to` is accepted. - When not specified, the functions will default to either sparse ``csr`` - or dense representation depending on the created object density. + When set, functions creating :class:`Qobj`, such as :func:"qeye" or + :func:"rand_herm", will use the specified data type. Any data-layer + known to ``qutip.data.to`` is accepted. + When ``None``, these functions will default to a sensible data type. """ _options = { # use auto tidyup From eaf15e8910ad5e183226628c7345153a25f6a4a0 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 20 Feb 2023 11:50:53 -0500 Subject: [PATCH 085/602] Fix docstrings --- qutip/core/cy/qobjevo.pyx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 34d95edda6..8333830e41 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -801,8 +801,8 @@ cdef class QobjEvo: constant parts, ``[Qobj, Coefficient]`` for coefficient based term. The original format of the :class:`Coefficient` is not restored. Lastly if the original `QobjEvo` is constructed with a function - returning a Qobj, the term is returned as a pair of :class:`Qobj` - and args (``dict``). + returning a Qobj, the term is returned as a pair of the original + function and args (``dict``). """ out = [] for element in self.elements: @@ -861,9 +861,15 @@ cdef class QobjEvo: ---------- t : float Time of the operator to apply. + state : Qobj right matrix of the product + check_real : bool (True) + Whether to convert the result to a `real` when the imaginary part + is smaller than the real part by a dactor of + ``settings.core['rtol']``. + Returns ------- expect : float or complex From 9ef000a54c4071d75b1ebc5901a2fd4fcbfd9fc6 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 20 Feb 2023 17:19:02 -0500 Subject: [PATCH 086/602] Finish rouchon + test for the method --- qutip/solver/sode/__init__.py | 5 +- qutip/solver/sode/_sode.pyx | 42 +++---- .../sode/{taylorapprox.py => itotaylor.py} | 3 +- qutip/solver/sode/noise.py | 6 +- qutip/solver/sode/rouchon.py | 106 ++++++++++++++++-- qutip/solver/sode/sode.py | 39 ++----- qutip/solver/sode/ssystem.pyx | 1 - qutip/tests/solver/test_sode_method.py | 75 ++++++++++++- 8 files changed, 204 insertions(+), 73 deletions(-) rename qutip/solver/sode/{taylorapprox.py => itotaylor.py} (94%) diff --git a/qutip/solver/sode/__init__.py b/qutip/solver/sode/__init__.py index ac3863eabd..5f9e2560fb 100644 --- a/qutip/solver/sode/__init__.py +++ b/qutip/solver/sode/__init__.py @@ -1,3 +1,4 @@ -from .sode import * from .ssystem import * -from .taylorapprox import * +from .sode import * +from .itotaylor import * +from .rouchon import * diff --git a/qutip/solver/sode/_sode.pyx b/qutip/solver/sode/_sode.pyx index ffdb8bdbe8..d86102c448 100644 --- a/qutip/solver/sode/_sode.pyx +++ b/qutip/solver/sode/_sode.pyx @@ -23,7 +23,7 @@ cdef class Euler: cdef int i cdef Data out for i in range(ntraj): - state = self.step(t, state, dt, dW[i, :, :]) + state = self.step(t + i * dt, state, dt, dW[i, :, :]) return state @cython.boundscheck(False) @@ -43,7 +43,7 @@ cdef class Euler: b = system.diffusion(t, state) cdef Data new_state = _data.add(state, a, dt) for i in range(system.num_collapse): - new_state = _data.add(new_state, b[i], dW[i, 0]) + new_state = _data.add(new_state, b[i], dW[0, i]) return new_state @@ -81,7 +81,7 @@ cdef class Platen(Euler): for i in range(num_ops): Vp.append(_data.add(d1, d2[i], sqrt_dt)) Vm.append(_data.add(d1, d2[i], -sqrt_dt)) - Vt = _data.add(Vt, d2[i], dW[i, 0]) + Vt = _data.add(Vt, d2[i], dW[0, i]) d1 = system.drift(t, Vt) out = _data.add(out, d1, 0.5 * dt) @@ -89,13 +89,13 @@ cdef class Platen(Euler): for i in range(num_ops): d2p = system.diffusion(t, Vp[i]) d2m = system.diffusion(t, Vm[i]) - dw = dW[i, 0] * 0.25 + dw = dW[0, i] * 0.25 out = _data.add(out, d2m[i], dw) out = _data.add(out, d2[i], 2 * dw) out = _data.add(out, d2p[i], dw) for j in range(num_ops): - dw2 = sqrt_dt_inv * (dW[i, 0] * dW[j, 0] - dt * (i == j)) + dw2 = sqrt_dt_inv * (dW[0, i] * dW[0, j] - dt * (i == j)) out = _data.add(out, d2p[j], dw2) out = _data.add(out, d2m[j], -dw2) @@ -122,8 +122,8 @@ cdef class Explicit15(Euler): dw = np.empty(num_ops) dz = np.empty(num_ops) for i in range(num_ops): - dw[i] = dW[i, 0] - dz[i] = 0.5 *(dW[i, 0] + 1./np.sqrt(3) * dW[i, 1]) + dw[i] = dW[0, i] + dz[i] = 0.5 *(dW[0, i] + 1./np.sqrt(3) * dW[1, i]) d1 = system.drift(t, state) d2 = system.diffusion(t, state) @@ -239,7 +239,7 @@ cdef class Milstein: state = state.copy() for i in range(ntraj): - self.step(t, state, dt, dW[i, :, :], out) + self.step(t + i * dt, state, dt, dW[i, :, :], out) state, out = out, state return state @@ -265,14 +265,14 @@ cdef class Milstein: _data.iadd_dense(out, system.a(), dt) for i in range(num_ops): - _data.iadd_dense(out, system.bi(i), dW[i, 0]) + _data.iadd_dense(out, system.bi(i), dW[0, i]) for i in range(num_ops): for j in range(i, num_ops): if i == j: - dw = (dW[i, 0] * dW[j, 0] - dt) * 0.5 + dw = (dW[0, i] * dW[0, j] - dt) * 0.5 else: - dw = dW[i, 0] * dW[j, 0] + dw = dW[0, i] * dW[0, j] _data.iadd_dense(out, system.Libj(i, j), dw) @@ -296,7 +296,7 @@ cdef class PredCorr: state = state.copy() for i in range(ntraj): - self.step(t, state, dt, dW[i, :, :], out) + self.step(t + i * dt, state, dt, dW[i, :, :], out) state, out = out, state return state @@ -320,24 +320,24 @@ cdef class PredCorr: _data.imul_dense(out, 0.) _data.iadd_dense(out, state, 1) - _data.iadd_dense(out, system.a(), dt*(1-alpha)) + _data.iadd_dense(out, system.a(), dt * (1-alpha)) _data.imul_dense(euler, 0.) _data.iadd_dense(euler, state, 1) _data.iadd_dense(euler, system.a(), dt) for i in range(num_ops): - _data.iadd_dense(euler, system.bi(i), dW[i, 0]) - _data.iadd_dense(out, system.bi(i), dW[i, 0]*eta) - _data.iadd_dense(out, system.Libj(i, i), dt*(alpha-1)*0.5) + _data.iadd_dense(euler, system.bi(i), dW[0, i]) + _data.iadd_dense(out, system.bi(i), dW[0, i] * eta) + _data.iadd_dense(out, system.Libj(i, i), dt * (alpha-1) * 0.5) system.set_state(t+dt, euler) if alpha: _data.iadd_dense(out, system.a(), dt*alpha) for i in range(num_ops): - _data.iadd_dense(out, system.bi(i), dW[i, 0]*(1-eta)) - _data.iadd_dense(out, system.Libj(i, i), -dt*alpha*0.5) + _data.iadd_dense(out, system.bi(i), dW[0, i] * (1-eta)) + _data.iadd_dense(out, system.Libj(i, i), -dt * alpha * 0.5) return out @@ -352,7 +352,7 @@ cdef class Taylor15(Milstein): state = state.copy() for i in range(ntraj): - self.step(t, state, dt, dW[i, :, :], out) + self.step(t + i * dt, state, dt, dW[i, :, :], out) state, out = out, state return state @@ -370,8 +370,8 @@ cdef class Taylor15(Milstein): cdef double[:] dz, dw num_ops = system.num_collapse - dw = dW[:, 0] - dz = 0.5 *(dW[:, 0] + 1./np.sqrt(3) * dW[:, 1]) * dt + dw = dW[0, :] + dz = 0.5 * (dW[0, :] + dW[1, :] / np.sqrt(3)) * dt _data.imul_dense(out, 0.) _data.iadd_dense(out, state, 1) diff --git a/qutip/solver/sode/taylorapprox.py b/qutip/solver/sode/itotaylor.py similarity index 94% rename from qutip/solver/sode/taylorapprox.py rename to qutip/solver/sode/itotaylor.py index 3c0c36edf5..0f0d895bc0 100644 --- a/qutip/solver/sode/taylorapprox.py +++ b/qutip/solver/sode/itotaylor.py @@ -61,7 +61,6 @@ class Explicit1_5_SODE(_Explicit_Simple_Integrator): StochasticSolver.add_integrator(EulerSODE, "euler") -StochasticSolver.add_integrator(EulerSODE, "euler-maruyama") StochasticSolver.add_integrator(Explicit1_5_SODE, "explicit1.5") -SMESolver.add_integrator(Taylor1_5_SODE, "taylor15") +SMESolver.add_integrator(Taylor1_5_SODE, "taylor1.5") SMESolver.add_integrator(Milstein_SODE, "milstein") diff --git a/qutip/solver/sode/noise.py b/qutip/solver/sode/noise.py index b02681970d..891358e6ea 100644 --- a/qutip/solver/sode/noise.py +++ b/qutip/solver/sode/noise.py @@ -39,9 +39,9 @@ def dW(self, dt): noise = self.noise.copy() if noise.shape[0] % N: noise = noise[:-(noise.shape[0] % N)] - out = np.empty((noise.shape[0] // N, self.num, 2), dtype=float) - out[:, :, 0] = noise.reshape(-1, N, self.num).sum(axis=1) - out[:, :, 1] = np.einsum( + out = np.empty((noise.shape[0] // N, 2, self.num), dtype=float) + out[:, 0, :] = noise.reshape(-1, N, self.num).sum(axis=1) + out[:, 1, :] = np.einsum( "ijk,j->ik", self.noise.reshape(-1, N, self.num), np.arange(N-0.5, 0, -1) diff --git a/qutip/solver/sode/rouchon.py b/qutip/solver/sode/rouchon.py index 2bd4e89c03..c827f7873e 100644 --- a/qutip/solver/sode/rouchon.py +++ b/qutip/solver/sode/rouchon.py @@ -1,11 +1,34 @@ - - -import qutip as qt import numpy as np +from qutip import unstack_columns, stack_columns from qutip.core import data as _data +from ..stochastic import SMESolver +from .sode import SIntegrator +from ..integrator.integrator import Integrator + + +__all__ = ["RouchonSODE"] + + +class RouchonSODE(SIntegrator): + """ + Scheme keeping the positivity of the density matrix + (:obj:`~smesolve` only). + See eq. (4) Pierre Rouchon and Jason F. Ralpha, + *Efficient Quantum Filtering for Quantum Feedback Control*, + `arXiv:1410.5345 [quant-ph] `_, + Phys. Rev. A 91, 012118, (2015). + + - Order: strong 1 + """ + integrator_options = { + "dt": 0.001, + "tol": 1e-7, + } -class Rouchon: def __init__(self, rhs, options): + self._options = self.integrator_options.copy() + self.options = options + self.H = rhs.H if self.H.issuper: raise TypeError("...") @@ -26,12 +49,59 @@ def __init__(self, rhs, options): self.id = _data.identity[dtype](self.H.shape[0]) - def step(self, t, state, dt, dW): + def set_state(self, t, state0, generator): + """ + Set the state of the SODE solver. + + Parameters + ---------- + t : float + Initial time + + state0 : qutip.Data + Initial state. + + generator : numpy.random.generator + Random number generator. + """ + self.t = t + self.state = unstack_columns(state0) + self.generator = generator + + def get_state(self, copy=True): + return self.t, stack_columns(self.state), self.generator + + def integrate(self, t, copy=True): + delta_t = (t - self.t) + if delta_t < 0: + raise ValueError("Stochastic integration time") + elif delta_t == 0: + return self.t, self.state, np.zeros(self.N_dw) + + dt = self.options["dt"] + N, extra = np.divmod(delta_t, dt) + N = int(N) + if extra > self.options["tol"]: + # Not a whole number of steps. + N += 1 + dt = delta_t / N + dW = self.generator.normal( + 0, + np.sqrt(dt), + size=(N, self.num_collapses) + ) + for dw in dW: + self.state = self._step(self.t, self.state, dt, dw) + self.t += dt + + return self.t, stack_columns(self.state), np.sum(dW, axis=0) + + def _step(self, t, state, dt, dW): # Same output as old rouchon up to nuerical error 1e-16 # But 7x slower dy = [ op.expect_data(t, state) * dt + dw - for op, dw in zip(self.cpcds, dW[:, 0]) + for op, dw in zip(self.cpcds, dW) ] M = _data.add(self.id, self.M._call(t), dt) for i in range(self.num_collapses): @@ -39,8 +109,30 @@ def step(self, t, state, dt, dW): M = _data.add(M, self.scc[i][i]._call(t), (dy[i]**2-dt)/2) for j in range(i): M = _data.add(M, self.scc[i][j]._call(t), dy[i]*dy[j]) - out = M @ state @ M.adjoint() + temp = _data.matmul(M, state) + Mdag = M.adjoint() + out = _data.matmul(temp, Mdag) for cop in self.c_ops: op = cop._call(t) out += op @ state @ op.adjoint() * dt return out / _data.trace(out) + + @property + def options(self): + """ + Supported options by Explicit Stochastic Integrators: + + dt : float, default=0.001 + Internal time step. + + tol : float, default=1e-7 + Relative tolerance. + """ + return self._options + + @options.setter + def options(self, new_options): + Integrator.options.fset(self, new_options) + + +SMESolver.add_integrator(RouchonSODE, "rouchon") diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 1201ddae8a..d996398a2b 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -2,7 +2,6 @@ from . import _sode from ..integrator.integrator import Integrator from ..stochastic import StochasticSolver, SMESolver -from .rouchon import Rouchon __all__ = ["SIntegrator", "PlatenSODE", "PredCorr_SODE"] @@ -60,8 +59,12 @@ def set_state(self, t, state0, generator): generator : numpy.random.generator Random number generator. """ - raise NotImplementedError + self.t = t + self.state = state0 + self.generator = generator + def get_state(self, copy=True): + return self.t, self.state, self.generator def integrate(self, t, copy=True): """ @@ -107,11 +110,6 @@ def __init__(self, rhs, options): self.options = options self.step_func = self.stepper(self.system).run - def set_state(self, t, state0, generator): - self.t = t - self.state = state0 - self.generator = generator - def integrate(self, t, copy=True): delta_t = (t - self.t) if delta_t < 0: @@ -129,16 +127,13 @@ def integrate(self, t, copy=True): dW = self.generator.normal( 0, np.sqrt(dt), - size=(N, self.system.num_collapse, self.N_dw) + size=(N, self.N_dw, self.system.num_collapse) ) self.state = self.step_func(self.t, self.state, dt, dW, N) self.t += dt * N - return self.t, self.state, np.sum(dW[:, :, 0], axis=0) - - def get_state(self, copy=True): - return self.t, self.state, self.generator + return self.t, self.state, np.sum(dW[:, 0, :], axis=0) @property def options(self): @@ -205,25 +200,5 @@ def __init__(self, rhs, options): ).run -class RouchonSODE(SIntegrator): - """ - Scheme keeping the positivity of the density matrix - (:obj:`~smesolve` only). - See eq. (4) Pierre Rouchon and Jason F. Ralpha, - *Efficient Quantum Filtering for Quantum Feedback Control*, - `arXiv:1410.5345 [quant-ph] `_, - Phys. Rev. A 91, 012118, (2015). - - - Order: strong 1 - """ - stepper = _sode.Platen - N_dw = 1 - - def __init__(self, rhs, options): - self._options = self.integrator_options.copy() - self.options = options - self.step_func = Rouchon(rhs) - - StochasticSolver.add_integrator(PlatenSODE, "platen") SMESolver.add_integrator(PredCorr_SODE, "pred_corr") diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index 7250c8779b..1151de3506 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -147,7 +147,6 @@ cdef class StochasticOpenSystem(_StochasticSystem): cdef readonly complex[:, :, :, ::1] _LLb def __init__(self, H, sc_ops, c_ops=(), heterodyne=False): - print(H, sc_ops, c_ops, False) if H.issuper: self.L = H + liouvillian(None, sc_ops) else: diff --git a/qutip/tests/solver/test_sode_method.py b/qutip/tests/solver/test_sode_method.py index dd057eaa79..3b9ad0c4fe 100644 --- a/qutip/tests/solver/test_sode_method.py +++ b/qutip/tests/solver/test_sode_method.py @@ -1,11 +1,12 @@ import numpy as np from itertools import product from qutip.core import data as _data -from qutip import qeye, destroy, QobjEvo, rand_ket +from qutip import qeye, destroy, QobjEvo, rand_ket, rand_herm, create, Qobj, operator_to_vector, fock_dm import qutip.solver.sode._sode as _sode import pytest -from qutip.solver.sode.ssystem import SimpleStochasticSystem +from qutip.solver.sode.ssystem import SimpleStochasticSystem, StochasticOpenSystem from qutip.solver.sode.noise import _Noise +from qutip.solver.stochastic import SMESolver, StochasticRHS def get_error_order(system, state, method, plot=False, **kw): @@ -28,16 +29,22 @@ def get_error_order(system, state, method, plot=False, **kw): if plot: import matplotlib.pyplot as plt plt.loglog(ts, err) - return np.polyfit(np.log(ts), np.log(err), 1)[0] + return np.polyfit(np.log(ts), np.log(err + 1e-20), 1)[0] def _make_oper(kind, N): if kind == "qeye": out = qeye(N) * np.random.rand() + elif kind == "create": + out = destroy(N) * np.random.rand() elif kind == "destroy": out = destroy(N) * np.random.rand() elif kind == "destroy2": out = destroy(N)**2 * np.random.rand() + elif kind == "herm": + out = rand_herm(N) + elif kind == "random": + out = Qobj(np.random.randn(N, N) + 1j * np.random.rand(N, N)) return QobjEvo(out) @@ -52,8 +59,8 @@ def _make_oper(kind, N): ]) @pytest.mark.parametrize(['H', 'c_ops'], [ pytest.param("qeye", ["destroy"], id='simple'), - pytest.param("destroy", ["destroy"], id='simple'), - pytest.param("qeye", ["qeye", "destroy", "destroy2"], id='2 c_ops'), + pytest.param("destroy", ["destroy"], id='destroy'), + pytest.param("qeye", ["qeye", "destroy", "destroy2"], id='3 c_ops'), ]) def test_methods(H, c_ops, method, order, kw): N = 5 @@ -65,3 +72,61 @@ def test_methods(H, c_ops, method, order, kw): # The first error term of the method is dt**0.5 greater than the solver # order. assert (order + 0.35) < error_order + + +def get_error_order_integrator(integrator, ref_integrator, state, plot=False): + ts = [ + 0.000001, 0.000002, 0.000005, 0.00001, 0.00002, 0.00005, + 0.0001, 0.0002, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, + ] + # state = rand_ket(system.dims[0]).data + err = np.zeros(len(ts), dtype=float) + for i, t in enumerate(ts): + integrator.options["dt"] = 0.1 + ref_integrator.options["dt"] = 0.1 + integrator.set_state(0., state, np.random.default_rng(0)) + ref_integrator.set_state(0., state, np.random.default_rng(0)) + out = integrator.integrate(t)[1] + target = ref_integrator.integrate(t)[1] + err[i] = _data.norm.l2(out - target) + + if plot: + import matplotlib.pyplot as plt + plt.loglog(ts, err) + if np.all(err < 1e-12): + # Exact match + return np.inf + return np.polyfit(np.log(ts), np.log(err + 1e-20), 1)[0] + + +@pytest.mark.parametrize(["method", "order"], [ + pytest.param("euler", 0.5, id="Euler"), + pytest.param("milstein", 1.0, id="Milstein"), + pytest.param("platen", 1.0, id="Platen"), + pytest.param("pred_corr", 1.0, id="PredCorr"), + pytest.param("rouchon", 1.0, id="rouchon"), + pytest.param("explicit1.5", 1.5, id="Explicit15"), +]) +@pytest.mark.parametrize(['H', 'c_ops', 'sc_ops'], [ + pytest.param("qeye", [], ["destroy"], id='simple'), + pytest.param("qeye", ["destroy"], ["destroy"], id='simple + collapse'), + pytest.param("herm", ["destroy", "destroy2"], [], id='2 c_ops'), + pytest.param("herm", [], ["destroy", "destroy2"], id='2 sc_ops'), + pytest.param("herm", ["create", "destroy"], ["destroy", "destroy2"], + id='many terms'), + pytest.param("herm", [], ["random"], id='random'), + pytest.param("herm", ["random"], ["random"], id='complex'), +]) +def test_integrator(method, order, H, c_ops, sc_ops): + N = 5 + H = _make_oper(H, N) + c_ops = [_make_oper(op, N) for op in c_ops] + sc_ops = [_make_oper(op, N) for op in sc_ops] + + rhs = StochasticRHS(StochasticOpenSystem, H, sc_ops, c_ops, False) + ref_sode = SMESolver.avail_integrators()["taylor1.5"](rhs, {"dt": 0.01}) + sode = SMESolver.avail_integrators()[method](rhs, {"dt": 0.01}) + state = operator_to_vector(fock_dm(5, 3, dtype="Dense")).data + + error_order = get_error_order_integrator(sode, ref_sode, state) + assert (order + 0.35) < error_order From 3884750036f08644562e4b60d29b89fe0982b502 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 21 Feb 2023 08:36:01 -0500 Subject: [PATCH 087/602] Add integrator test for time-dependant cases --- qutip/solver/sode/ssystem.pyx | 14 ++++++++++---- qutip/tests/solver/test_sode_method.py | 25 +++++++++++++++++-------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index 1151de3506..3f06907bc6 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -462,10 +462,16 @@ cdef class SimpleStochasticSystem(_StochasticSystem): def analytic(self, t, W): """ - Analytic solution, H and all c_ops must commute and are constant. + Analytic solution, H and all c_ops must commute. + Support time dependance of order 2 (a + b*t + c*t**2) """ - out = self.H(0) * t + def _intergal(f, T): + return (f(0) + 4 * f(T/2) + f(T)) / 6 + + out = _intergal(self.H, t) * t for i in range(self.num_collapse): - out += self.c_ops[i](0) * W[i] - out -= 0.5 * self.c_ops[i](0) @ self.c_ops[i](0) * t + out += _intergal(self.c_ops[i], t) * W[i] + out -= 0.5 * _intergal( + lambda t: self.c_ops[i](t) @ self.c_ops[i](t), t + ) * t return out.expm().data diff --git a/qutip/tests/solver/test_sode_method.py b/qutip/tests/solver/test_sode_method.py index 3b9ad0c4fe..64849f39c0 100644 --- a/qutip/tests/solver/test_sode_method.py +++ b/qutip/tests/solver/test_sode_method.py @@ -33,16 +33,21 @@ def get_error_order(system, state, method, plot=False, **kw): def _make_oper(kind, N): + a = destroy(N) if kind == "qeye": out = qeye(N) * np.random.rand() elif kind == "create": - out = destroy(N) * np.random.rand() + out = a.dag() * np.random.rand() elif kind == "destroy": - out = destroy(N) * np.random.rand() + out = a * np.random.rand() + elif kind == "destroy td": + out = [a, lambda t: 1 + t/2] elif kind == "destroy2": - out = destroy(N)**2 * np.random.rand() + out = a**2 elif kind == "herm": out = rand_herm(N) + elif kind == "herm td": + out = [rand_herm(N), lambda t: -1 + t/2 + t**2] elif kind == "random": out = Qobj(np.random.randn(N, N) + 1j * np.random.rand(N, N)) return QobjEvo(out) @@ -57,16 +62,18 @@ def _make_oper(kind, N): pytest.param("Taylor15", 1.5, {}, id="Taylor15"), pytest.param("Explicit15", 1.5, {}, id="Explicit15"), ]) -@pytest.mark.parametrize(['H', 'c_ops'], [ +@pytest.mark.parametrize(['H', 'sc_ops'], [ pytest.param("qeye", ["destroy"], id='simple'), pytest.param("destroy", ["destroy"], id='destroy'), - pytest.param("qeye", ["qeye", "destroy", "destroy2"], id='3 c_ops'), + pytest.param("destroy", ["destroy td"], id='sc_ops td'), + pytest.param("herm td", ["qeye"], id='H td'), + pytest.param("qeye", ["qeye", "destroy", "destroy2"], id='3 sc_ops'), ]) -def test_methods(H, c_ops, method, order, kw): +def test_methods(H, sc_ops, method, order, kw): N = 5 H = _make_oper(H, N) - c_ops = [_make_oper(op, N) for op in c_ops] - system = SimpleStochasticSystem(H, c_ops) + sc_ops = [_make_oper(op, N) for op in sc_ops] + system = SimpleStochasticSystem(H, sc_ops) state = rand_ket(N).data error_order = get_error_order(system, state, method, **kw) # The first error term of the method is dt**0.5 greater than the solver @@ -116,6 +123,8 @@ def get_error_order_integrator(integrator, ref_integrator, state, plot=False): id='many terms'), pytest.param("herm", [], ["random"], id='random'), pytest.param("herm", ["random"], ["random"], id='complex'), + pytest.param("herm td", ["random"], ["destroy"], id='H td'), + pytest.param("herm", ["random"], ["destroy td"], id='sc_ops td'), ]) def test_integrator(method, order, H, c_ops, sc_ops): N = 5 From f6929e5492a0e80e13a093624bd5de32aa01cdf8 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 21 Feb 2023 10:09:53 -0500 Subject: [PATCH 088/602] Improve coefficient docstring --- qutip/core/coefficient.py | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/qutip/core/coefficient.py b/qutip/core/coefficient.py index 2d702fd0c9..efd754806f 100644 --- a/qutip/core/coefficient.py +++ b/qutip/core/coefficient.py @@ -65,9 +65,14 @@ def _return(base, **kwargs): def coefficient(base, *, tlist=None, args={}, args_ctypes={}, order=3, compile_opt=None, function_style=None, **kwargs): - """Coefficient for time dependent systems. + """Build ``Coefficient`` for time dependent systems: - The coefficients are either a function, a string or a numpy array. + ``` + QobjEvo = Qobj + Qobj * Coefficient + Qobj * Coefficient + ... + ``` + + The coefficients can be a function, a string or a numpy array. Other + packages may add support for other kind of coefficients. For function based coefficients, the function signature must be either: @@ -104,8 +109,7 @@ def f2_t(t, args): real imag conj abs norm arg proj numpy as np, scipy.special as spe (python interface) - and cython_special (cython interface) - [https://docs.scipy.org/doc/scipy/reference/special.cython_special.html]. + and cython_special (scipy cython interface) *Examples* coeff = coefficient('exp(-1j*w1*t)', args={"w1":1.}) @@ -132,6 +136,32 @@ def f2_t(t, args): created from ``ndarray``). Other interpolation methods from scipy are converted to a function-based coefficient (the same kind of coefficient created from callables). + + Parameters + ---------- + base : object + Base object to make into a Coefficient. + + args : dict, optional + Dictionary of arguments to pass to the function or string coefficient. + + order : int, default=3 + Order of the spline for array based coefficient. + + tlist : iterable, optional + Times for each element of an array based coefficient. + + function_style : str, ["dict", "pythonic", None] + Function signature of function based coefficients. + + args_ctypes : dict, optional + C type for the args when compiling array based coefficients. + + compile_opt : CompilationOptions, optional + Sets of options for the compilation of string based coefficients. + + **kwargs + Extra arguments to pass the the coefficients. """ kwargs.update({ "tlist": tlist, From 75a353d7cd751f606ca1467b5a23798f8f77573f Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 21 Feb 2023 10:33:02 -0500 Subject: [PATCH 089/602] Improve docstring format --- qutip/core/options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutip/core/options.py b/qutip/core/options.py index 5a60ffa509..fe035d6375 100644 --- a/qutip/core/options.py +++ b/qutip/core/options.py @@ -93,8 +93,8 @@ class CoreOptions(QutipOptions): default_dtype : Nonetype, str, type {None} When set, functions creating :class:`Qobj`, such as :func:"qeye" or :func:"rand_herm", will use the specified data type. Any data-layer - known to ``qutip.data.to`` is accepted. - When ``None``, these functions will default to a sensible data type. + known to ``qutip.data.to`` is accepted. When ``None``, these functions + will default to a sensible data type. """ _options = { # use auto tidyup From 158660f2177014242bb3180f8c91f4453ca5dcb2 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 21 Feb 2023 18:02:09 -0500 Subject: [PATCH 090/602] sse derivative --- qutip/solver/sode/ssystem.pxd | 2 +- qutip/solver/sode/ssystem.pyx | 116 ++++++++++++++++++- qutip/tests/solver/test_stochastic_system.py | 38 +++--- 3 files changed, 134 insertions(+), 22 deletions(-) diff --git a/qutip/solver/sode/ssystem.pxd b/qutip/solver/sode/ssystem.pxd index f3c5a5e5ef..ae1900e201 100644 --- a/qutip/solver/sode/ssystem.pxd +++ b/qutip/solver/sode/ssystem.pxd @@ -14,7 +14,7 @@ cdef class _StochasticSystem: cpdef list diffusion(self, t, Data state) - cpdef void set_state(self, double t, Data state) + cpdef void set_state(self, double t, Data state) except * cpdef Data a(self) cpdef Data bi(self, int i) cpdef Data Libj(self, int i, int j) diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index 3f06907bc6..3bc0076ced 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -33,7 +33,7 @@ cdef class _StochasticSystem: cpdef list diffusion(self, t, Data state): raise NotImplementedError - cpdef void set_state(self, double t, Data state): + cpdef void set_state(self, double t, Data state) except *: self.t = t self.state = state self._is_set = True @@ -82,14 +82,21 @@ cdef class _StochasticSystem: cdef class StochasticClosedSystem(_StochasticSystem): - cdef QobjEvo H - cdef list c_ops - cdef list cpcd_ops + cdef readonly QobjEvo H + cdef readonly list c_ops + cdef readonly list cpcd_ops + cdef bint _a_set, _b_set, _Lb_set + + cdef readonly Dense _a, temp + cdef readonly complex[::1] _e + cdef readonly complex[:, ::1] _b, _La, _c_vec + cdef readonly complex[:, :, ::1] _Lb def __init__(self, H, sc_ops, heterodyne=False): self.H = -1j * H if heterodyne: self.c_ops = [] + self.cpcd_ops = [] for c_op in sc_ops: self.c_ops.append(c_op / np.sqrt(2)) self.c_ops.append(c_op * (-1j / np.sqrt(2))) @@ -104,6 +111,7 @@ cdef class StochasticClosedSystem(_StochasticSystem): self.H += -0.5 * c_op.dag() * c_op self.issuper = False self.dims = self.H.dims + self._is_set = False cpdef Data drift(self, t, Data state): cdef int i @@ -132,6 +140,102 @@ cdef class StochasticClosedSystem(_StochasticSystem): out.append(_data.add(_out, state, -0.5 * expect)) return out + cpdef void set_state(self, double t, Data state) except *: + cdef n, l + self.t = t + self.state = _data.to(_data.Dense, state).reorder(fortran=1) + self._a_set = False + self._b_set = False + self._Lb_set = False + + if not self._is_set: + n = self.num_collapse + l = self.H.shape[0] + self._is_set = 1 + self._a = dense.zeros(l, 1) + self.temp = dense.zeros(l, 1) + self._e = np.zeros(n, dtype=complex) + self._c_vec = np.zeros((n, l), dtype=complex) + self._b = np.zeros((n, l), dtype=complex) + self._Lb = np.zeros((n, n, l), dtype=complex) + + cpdef Data a(self): + if not self._a_set: + self._compute_a() + return self._a + + cdef void _compute_a(self) except *: + if not self._is_set: + raise RuntimeError + cdef Dense c_vec + _data.imul_dense(self._a, 0) + self.H.matmul_data(self.t, self.state, self._a) + + for i in range(self.num_collapse): + c_op = self.c_ops[i] + c_vec = _dense_wrap(self._c_vec[i, :]) + _data.imul_dense(c_vec, 0) + c_op.matmul_data(self.t, self.state, c_vec) + self._e[i] = _data.inner_dense(self.state, c_vec).real + _data.iadd_dense(self._a, self.state, -0.5 * self._e[i]**2) + _data.iadd_dense(self._a, c_vec, self._e[i]) + + self._a_set = True + + cpdef Data bi(self, int i): + if not self._b_set: + self._compute_b() + return _dense_wrap(self._b[i, :]) + + cdef void _compute_b(self) except *: + if not self._a_set: + self._compute_a() + cdef int i + cdef QobjEvo c_op + cdef Dense b_vec + for i in range(self.num_collapse): + c_op = self.c_ops[i] + b_vec = _dense_wrap(self._b[i, :]) + _data.imul_dense(b_vec, 0) + _data.iadd_dense(b_vec, _dense_wrap(self._c_vec[i, :])) + _data.iadd_dense(b_vec, self.state, -self._e[i]) + self._b_set = True + + cpdef Data Libj(self, int i, int j): + if not self._Lb_set: + self._compute_Lb() + # We only support commutative diffusion + if i > j: + j, i = i, j + return _dense_wrap(self._Lb[i, j, :]) + + cdef void _compute_Lb(self) except *: + cdef int i, j + cdef QobjEvo c_op + cdef Dense Lb_vec + cdef complex de_dx_b + if not self._b_set: + self._compute_b() + + for i in range(self.num_collapse): + c_op = self.c_ops[i] + for j in range(i, self.num_collapse): + Lb_vec = _dense_wrap(self._Lb[i, j, :]) + _data.imul_dense(Lb_vec, 0) + _data.imul_dense(self.temp, 0) + c_op.matmul_data(self.t, _dense_wrap(self._c_vec[j, :]), self.temp) + _data.iadd_dense(Lb_vec, self.temp) + _data.iadd_dense(Lb_vec, _dense_wrap(self._c_vec[i, :]), -0.5 * self._e[j]) + _data.iadd_dense(Lb_vec, _dense_wrap(self._c_vec[j, :]), -0.5 * self._e[i]) + de_dx_b = ( + self._e[i] * self._e[j] + - _data.inner_dense(_dense_wrap(self._c_vec[i, :]), _dense_wrap(self._c_vec[j, :])).real + - _data.inner_dense(self.temp, self.state).real + ) + _data.iadd_dense(Lb_vec, self.state, de_dx_b) + + self._Lb_set = True + cdef class StochasticOpenSystem(_StochasticSystem): cdef QobjEvo L @@ -184,7 +288,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): out.append(_data.add(vec, state, -expect)) return out - cpdef void set_state(self, double t, Data state): + cpdef void set_state(self, double t, Data state) except *: cdef n, l self.t = t self.state = _data.to(_data.Dense, state).reorder(fortran=1) @@ -422,7 +526,7 @@ cdef class SimpleStochasticSystem(_StochasticSystem): out.append(self.c_ops[i].matmul_data(t, state)) return out - cpdef void set_state(self, double t, Data state): + cpdef void set_state(self, double t, Data state) except *: self.t = t self.state = _data.to(_data.Dense, state) diff --git a/qutip/tests/solver/test_stochastic_system.py b/qutip/tests/solver/test_stochastic_system.py index 2cdfd9d5b6..645419e4fe 100644 --- a/qutip/tests/solver/test_stochastic_system.py +++ b/qutip/tests/solver/test_stochastic_system.py @@ -4,7 +4,7 @@ basis, rand_herm, fock_dm, liouvillian, operator_to_vector ) from qutip.solver.sode.ssystem import * -from qutip.solver.sode.ssystem import SimpleStochasticSystem +from qutip.solver.sode.ssystem import SimpleStochasticSystem, StochasticClosedSystem import qutip.core.data as _data import pytest from itertools import product @@ -99,14 +99,14 @@ def _check_equivalence(f, target, args): for dt in dts ]) if np.all(errors_dt < 1e-6): - return True + return power = np.polyfit(np.log(dts), np.log(errors_dt + 1e-16), 1)[0] # Sometime the dt term is cancelled and the dt**2 term is dominant - return power > 0.9 + assert power > 0.9 -def _run_derr_check(solver, state): +def _run_derr_check(solver, state, all): """ """ @@ -116,22 +116,28 @@ def _run_derr_check(solver, state): solver.set_state(t, state) assert _data.norm.l2(solver.drift(t, state) - solver.a()) < 1e-6 - assert _check_equivalence(L0(solver, a), solver.L0a(), (t, state)) - for i in range(N): b = lambda *args: solver.diffusion(*args)[i] assert b(t, state) == solver.bi(i) - assert _check_equivalence(L0(solver, b), solver.L0bi(i), (t, state)) - assert _check_equivalence(L(solver, i, a), solver.Lia(i), (t, state)) - for j in range(N): - assert _check_equivalence( + _check_equivalence( L(solver, j, b), solver.Libj(j, i), (t, state) ) + if not all: + # Most method only need the Libj term. + return + + _check_equivalence(L0(solver, a), solver.L0a(), (t, state)) + + for i in range(N): + b = lambda *args: solver.diffusion(*args)[i] + _check_equivalence(L0(solver, b), solver.L0bi(i), (t, state)) + _check_equivalence(L(solver, i, a), solver.Lia(i), (t, state)) + + for j in range(N): for k in range(N): - print(i, j, k) - assert _check_equivalence( + _check_equivalence( LL(solver, k, j, b), solver.LiLjbk(k, j, i), (t, state) ) @@ -160,17 +166,19 @@ def _make_oper(kind, N): pytest.param("qeye", ["td"], id='c_ops td'), pytest.param("rand", ["rand"], id='random'), ]) -@pytest.mark.parametrize('type', ["sme", "sse"]) @pytest.mark.parametrize('heterodyne', [False, True]) +@pytest.mark.parametrize('type', ["sse", "sme"]) def test_system(H, c_ops, type, heterodyne): N = 5 H = _make_oper(H, N) c_ops = [_make_oper(op, N) for op in c_ops] if type == "sse": - system = SimpleStochasticSystem(H, c_ops) + system = StochasticClosedSystem(H, c_ops, heterodyne=heterodyne) state = basis(N, N-2).data + all = False else: H = liouvillian(H) system = StochasticOpenSystem(H, c_ops, heterodyne=heterodyne) state = operator_to_vector(fock_dm(N, N-2)).data - _run_derr_check(system, state) + all = True + _run_derr_check(system, state, all) From 21a9cef7a88154a53a0faa886f099591fcd1295c Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 22 Feb 2023 14:16:02 -0500 Subject: [PATCH 091/602] sse derr only support one c_ops --- qutip/solver/sode/ssystem.pyx | 78 ++++++++------------ qutip/tests/solver/test_stochastic_system.py | 34 ++++++--- 2 files changed, 54 insertions(+), 58 deletions(-) diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index 3bc0076ced..1fcae1e4d9 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -87,10 +87,8 @@ cdef class StochasticClosedSystem(_StochasticSystem): cdef readonly list cpcd_ops cdef bint _a_set, _b_set, _Lb_set - cdef readonly Dense _a, temp - cdef readonly complex[::1] _e - cdef readonly complex[:, ::1] _b, _La, _c_vec - cdef readonly complex[:, :, ::1] _Lb + cdef readonly Dense _a, temp, _b_vec, _c_vec, _Lb + cdef readonly complex _e def __init__(self, H, sc_ops, heterodyne=False): self.H = -1j * H @@ -148,16 +146,17 @@ cdef class StochasticClosedSystem(_StochasticSystem): self._b_set = False self._Lb_set = False + if self.num_collapse != 1: + raise NotImplementedError + if not self._is_set: - n = self.num_collapse l = self.H.shape[0] self._is_set = 1 self._a = dense.zeros(l, 1) self.temp = dense.zeros(l, 1) - self._e = np.zeros(n, dtype=complex) - self._c_vec = np.zeros((n, l), dtype=complex) - self._b = np.zeros((n, l), dtype=complex) - self._Lb = np.zeros((n, n, l), dtype=complex) + self._c_vec = dense.zeros(l, 1) + self._b_vec = dense.zeros(l, 1) + self._Lb = dense.zeros(l, 1) cpdef Data a(self): if not self._a_set: @@ -167,47 +166,38 @@ cdef class StochasticClosedSystem(_StochasticSystem): cdef void _compute_a(self) except *: if not self._is_set: raise RuntimeError - cdef Dense c_vec _data.imul_dense(self._a, 0) self.H.matmul_data(self.t, self.state, self._a) - for i in range(self.num_collapse): - c_op = self.c_ops[i] - c_vec = _dense_wrap(self._c_vec[i, :]) - _data.imul_dense(c_vec, 0) - c_op.matmul_data(self.t, self.state, c_vec) - self._e[i] = _data.inner_dense(self.state, c_vec).real - _data.iadd_dense(self._a, self.state, -0.5 * self._e[i]**2) - _data.iadd_dense(self._a, c_vec, self._e[i]) + c_op = self.c_ops[0] + _data.imul_dense(self._c_vec, 0) + c_op.matmul_data(self.t, self.state, self._c_vec) + self._e = _data.inner_dense(self.state, self._c_vec).real + _data.iadd_dense(self._a, self.state, -0.5 * self._e**2) + _data.iadd_dense(self._a, self._c_vec, self._e) self._a_set = True cpdef Data bi(self, int i): if not self._b_set: self._compute_b() - return _dense_wrap(self._b[i, :]) + return self._b_vec cdef void _compute_b(self) except *: if not self._a_set: self._compute_a() cdef int i cdef QobjEvo c_op - cdef Dense b_vec - for i in range(self.num_collapse): - c_op = self.c_ops[i] - b_vec = _dense_wrap(self._b[i, :]) - _data.imul_dense(b_vec, 0) - _data.iadd_dense(b_vec, _dense_wrap(self._c_vec[i, :])) - _data.iadd_dense(b_vec, self.state, -self._e[i]) + c_op = self.c_ops[0] + _data.imul_dense(self._b_vec, 0) + _data.iadd_dense(self._b_vec, self._c_vec) + _data.iadd_dense(self._b_vec, self.state, -self._e) self._b_set = True cpdef Data Libj(self, int i, int j): if not self._Lb_set: self._compute_Lb() - # We only support commutative diffusion - if i > j: - j, i = i, j - return _dense_wrap(self._Lb[i, j, :]) + return self._Lb cdef void _compute_Lb(self) except *: cdef int i, j @@ -217,22 +207,18 @@ cdef class StochasticClosedSystem(_StochasticSystem): if not self._b_set: self._compute_b() - for i in range(self.num_collapse): - c_op = self.c_ops[i] - for j in range(i, self.num_collapse): - Lb_vec = _dense_wrap(self._Lb[i, j, :]) - _data.imul_dense(Lb_vec, 0) - _data.imul_dense(self.temp, 0) - c_op.matmul_data(self.t, _dense_wrap(self._c_vec[j, :]), self.temp) - _data.iadd_dense(Lb_vec, self.temp) - _data.iadd_dense(Lb_vec, _dense_wrap(self._c_vec[i, :]), -0.5 * self._e[j]) - _data.iadd_dense(Lb_vec, _dense_wrap(self._c_vec[j, :]), -0.5 * self._e[i]) - de_dx_b = ( - self._e[i] * self._e[j] - - _data.inner_dense(_dense_wrap(self._c_vec[i, :]), _dense_wrap(self._c_vec[j, :])).real - - _data.inner_dense(self.temp, self.state).real - ) - _data.iadd_dense(Lb_vec, self.state, de_dx_b) + c_op = self.c_ops[0] + _data.imul_dense(self._Lb, 0) + _data.imul_dense(self.temp, 0) + c_op.matmul_data(self.t, self._c_vec, self.temp) + _data.iadd_dense(self._Lb, self.temp) + _data.iadd_dense(self._Lb, self._c_vec, -self._e) + de_dx_b = ( + self._e * self._e + - _data.inner_dense(self._c_vec, self._c_vec).real + - _data.inner_dense(self.temp, self.state).real + ) + _data.iadd_dense(self._Lb, self.state, de_dx_b) self._Lb_set = True diff --git a/qutip/tests/solver/test_stochastic_system.py b/qutip/tests/solver/test_stochastic_system.py index 645419e4fe..fb883300f2 100644 --- a/qutip/tests/solver/test_stochastic_system.py +++ b/qutip/tests/solver/test_stochastic_system.py @@ -167,18 +167,28 @@ def _make_oper(kind, N): pytest.param("rand", ["rand"], id='random'), ]) @pytest.mark.parametrize('heterodyne', [False, True]) -@pytest.mark.parametrize('type', ["sse", "sme"]) -def test_system(H, c_ops, type, heterodyne): +def test_open_system(H, c_ops, heterodyne): N = 5 H = _make_oper(H, N) c_ops = [_make_oper(op, N) for op in c_ops] - if type == "sse": - system = StochasticClosedSystem(H, c_ops, heterodyne=heterodyne) - state = basis(N, N-2).data - all = False - else: - H = liouvillian(H) - system = StochasticOpenSystem(H, c_ops, heterodyne=heterodyne) - state = operator_to_vector(fock_dm(N, N-2)).data - all = True - _run_derr_check(system, state, all) + H = liouvillian(H) + system = StochasticOpenSystem(H, c_ops, heterodyne=heterodyne) + state = operator_to_vector(fock_dm(N, N-2)).data + _run_derr_check(system, state, True) + + +@pytest.mark.parametrize(['H', 'c_ops'], [ + pytest.param("qeye", ["destroy"], id='simple'), + pytest.param("tridiag", ["destroy"], id='simple'), + pytest.param("td", ["destroy"], id='H td'), + pytest.param("qeye", ["td"], id='c_ops td'), + pytest.param("rand", ["rand"], id='random'), +]) +def test_closed_system(H, c_ops): + N = 5 + H = _make_oper(H, N) + c_ops = [_make_oper(op, N) for op in c_ops] + system = StochasticClosedSystem(H, c_ops, heterodyne=False) + state = basis(N, N-2).data + all = False + _run_derr_check(system, state, False) From 27b46dbcc24b74dd1c62852d560c8f1d6b25c855 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 22 Feb 2023 14:17:42 -0500 Subject: [PATCH 092/602] sse derr only support one c_ops --- qutip/tests/solver/test_stochastic_system.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutip/tests/solver/test_stochastic_system.py b/qutip/tests/solver/test_stochastic_system.py index fb883300f2..ff1fc0ff26 100644 --- a/qutip/tests/solver/test_stochastic_system.py +++ b/qutip/tests/solver/test_stochastic_system.py @@ -1,6 +1,6 @@ import numpy as np from qutip import ( - qeye, num, destroy, create, QobjEvo, + qeye, num, destroy, create, QobjEvo, Qobj, basis, rand_herm, fock_dm, liouvillian, operator_to_vector ) from qutip.solver.sode.ssystem import * @@ -179,7 +179,7 @@ def test_open_system(H, c_ops, heterodyne): @pytest.mark.parametrize(['H', 'c_ops'], [ pytest.param("qeye", ["destroy"], id='simple'), - pytest.param("tridiag", ["destroy"], id='simple'), + pytest.param("tridiag", ["destroy"], id='tridiag'), pytest.param("td", ["destroy"], id='H td'), pytest.param("qeye", ["td"], id='c_ops td'), pytest.param("rand", ["rand"], id='random'), @@ -189,6 +189,6 @@ def test_closed_system(H, c_ops): H = _make_oper(H, N) c_ops = [_make_oper(op, N) for op in c_ops] system = StochasticClosedSystem(H, c_ops, heterodyne=False) - state = basis(N, N-2).data + state = Qobj([1]*N).unit().data all = False _run_derr_check(system, state, False) From ea32f31537923fe39aefdc85040ed9b4580138a4 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 23 Feb 2023 16:03:46 -0500 Subject: [PATCH 093/602] add rouchon for sse and rm milstein --- qutip/solver/sode/rouchon.py | 36 +++++--- qutip/solver/sode/ssystem.pxd | 2 - qutip/solver/sode/ssystem.pyx | 96 +------------------- qutip/tests/solver/test_sode_method.py | 30 +++++- qutip/tests/solver/test_stochastic_system.py | 25 +---- 5 files changed, 56 insertions(+), 133 deletions(-) diff --git a/qutip/solver/sode/rouchon.py b/qutip/solver/sode/rouchon.py index c827f7873e..03f9b98039 100644 --- a/qutip/solver/sode/rouchon.py +++ b/qutip/solver/sode/rouchon.py @@ -1,7 +1,7 @@ import numpy as np from qutip import unstack_columns, stack_columns from qutip.core import data as _data -from ..stochastic import SMESolver +from ..stochastic import StochasticSolver from .sode import SIntegrator from ..integrator.integrator import Integrator @@ -32,6 +32,7 @@ def __init__(self, rhs, options): self.H = rhs.H if self.H.issuper: raise TypeError("...") + self._issuper = rhs.issuper dtype = type(self.H(0).data) self.c_ops = rhs.c_ops self.sc_ops = rhs.sc_ops @@ -65,11 +66,11 @@ def set_state(self, t, state0, generator): Random number generator. """ self.t = t - self.state = unstack_columns(state0) + self.state = state0 self.generator = generator def get_state(self, copy=True): - return self.t, stack_columns(self.state), self.generator + return self.t, self.state, self.generator def integrate(self, t, copy=True): delta_t = (t - self.t) @@ -90,14 +91,19 @@ def integrate(self, t, copy=True): np.sqrt(dt), size=(N, self.num_collapses) ) + + if self._issuper: + self.state = unstack_columns(self.state) for dw in dW: self.state = self._step(self.t, self.state, dt, dw) self.t += dt + if self._issuper: + self.state = stack_columns(self.state) - return self.t, stack_columns(self.state), np.sum(dW, axis=0) + return self.t, self.state, np.sum(dW, axis=0) def _step(self, t, state, dt, dW): - # Same output as old rouchon up to nuerical error 1e-16 + # Same output as old rouchon up to numerical error # But 7x slower dy = [ op.expect_data(t, state) * dt + dw @@ -109,13 +115,17 @@ def _step(self, t, state, dt, dW): M = _data.add(M, self.scc[i][i]._call(t), (dy[i]**2-dt)/2) for j in range(i): M = _data.add(M, self.scc[i][j]._call(t), dy[i]*dy[j]) - temp = _data.matmul(M, state) - Mdag = M.adjoint() - out = _data.matmul(temp, Mdag) - for cop in self.c_ops: - op = cop._call(t) - out += op @ state @ op.adjoint() * dt - return out / _data.trace(out) + out = _data.matmul(M, state) + if self._issuper: + Mdag = M.adjoint() + out = _data.matmul(out, Mdag) + for cop in self.c_ops: + op = cop._call(t) + out += op @ state @ op.adjoint() * dt + out = out / _data.trace(out) + else: + out = out / _data.norm.l2(out) + return out @property def options(self): @@ -135,4 +145,4 @@ def options(self, new_options): Integrator.options.fset(self, new_options) -SMESolver.add_integrator(RouchonSODE, "rouchon") +StochasticSolver.add_integrator(RouchonSODE, "rouchon") diff --git a/qutip/solver/sode/ssystem.pxd b/qutip/solver/sode/ssystem.pxd index ae1900e201..77b2981ef1 100644 --- a/qutip/solver/sode/ssystem.pxd +++ b/qutip/solver/sode/ssystem.pxd @@ -7,8 +7,6 @@ cdef class _StochasticSystem: cdef readonly object dims cdef Data state cdef double t - cdef object imp - cdef int _is_set cpdef Data drift(self, t, Data state) diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index 1fcae1e4d9..e2d716a9ab 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -22,10 +22,7 @@ cdef Dense _dense_wrap(double complex [::1] x): cdef class _StochasticSystem: def __init__(self, a, b): - self.d1 = a - self.d2 = b - self.num_collapse = 1 - self._is_set = False + raise NotImplementedError cpdef Data drift(self, t, Data state): raise NotImplementedError @@ -34,9 +31,7 @@ cdef class _StochasticSystem: raise NotImplementedError cpdef void set_state(self, double t, Data state) except *: - self.t = t - self.state = state - self._is_set = True + raise NotImplementedError cpdef Data a(self): """ @@ -109,7 +104,6 @@ cdef class StochasticClosedSystem(_StochasticSystem): self.H += -0.5 * c_op.dag() * c_op self.issuper = False self.dims = self.H.dims - self._is_set = False cpdef Data drift(self, t, Data state): cdef int i @@ -138,96 +132,12 @@ cdef class StochasticClosedSystem(_StochasticSystem): out.append(_data.add(_out, state, -0.5 * expect)) return out - cpdef void set_state(self, double t, Data state) except *: - cdef n, l - self.t = t - self.state = _data.to(_data.Dense, state).reorder(fortran=1) - self._a_set = False - self._b_set = False - self._Lb_set = False - - if self.num_collapse != 1: - raise NotImplementedError - - if not self._is_set: - l = self.H.shape[0] - self._is_set = 1 - self._a = dense.zeros(l, 1) - self.temp = dense.zeros(l, 1) - self._c_vec = dense.zeros(l, 1) - self._b_vec = dense.zeros(l, 1) - self._Lb = dense.zeros(l, 1) - - cpdef Data a(self): - if not self._a_set: - self._compute_a() - return self._a - - cdef void _compute_a(self) except *: - if not self._is_set: - raise RuntimeError - _data.imul_dense(self._a, 0) - self.H.matmul_data(self.t, self.state, self._a) - - c_op = self.c_ops[0] - _data.imul_dense(self._c_vec, 0) - c_op.matmul_data(self.t, self.state, self._c_vec) - self._e = _data.inner_dense(self.state, self._c_vec).real - _data.iadd_dense(self._a, self.state, -0.5 * self._e**2) - _data.iadd_dense(self._a, self._c_vec, self._e) - - self._a_set = True - - cpdef Data bi(self, int i): - if not self._b_set: - self._compute_b() - return self._b_vec - - cdef void _compute_b(self) except *: - if not self._a_set: - self._compute_a() - cdef int i - cdef QobjEvo c_op - c_op = self.c_ops[0] - _data.imul_dense(self._b_vec, 0) - _data.iadd_dense(self._b_vec, self._c_vec) - _data.iadd_dense(self._b_vec, self.state, -self._e) - self._b_set = True - - cpdef Data Libj(self, int i, int j): - if not self._Lb_set: - self._compute_Lb() - return self._Lb - - cdef void _compute_Lb(self) except *: - cdef int i, j - cdef QobjEvo c_op - cdef Dense Lb_vec - cdef complex de_dx_b - if not self._b_set: - self._compute_b() - - c_op = self.c_ops[0] - _data.imul_dense(self._Lb, 0) - _data.imul_dense(self.temp, 0) - c_op.matmul_data(self.t, self._c_vec, self.temp) - _data.iadd_dense(self._Lb, self.temp) - _data.iadd_dense(self._Lb, self._c_vec, -self._e) - de_dx_b = ( - self._e * self._e - - _data.inner_dense(self._c_vec, self._c_vec).real - - _data.inner_dense(self.temp, self.state).real - ) - _data.iadd_dense(self._Lb, self.state, de_dx_b) - - self._Lb_set = True - - cdef class StochasticOpenSystem(_StochasticSystem): cdef QobjEvo L cdef list c_ops cdef int state_size cdef double dt + cdef int _is_set cdef bint _a_set, _b_set, _Lb_set, _L0b_set, _La_set, _LLb_set, _L0a_set cdef readonly Dense _a, temp, _L0a diff --git a/qutip/tests/solver/test_sode_method.py b/qutip/tests/solver/test_sode_method.py index 64849f39c0..440f9e28a7 100644 --- a/qutip/tests/solver/test_sode_method.py +++ b/qutip/tests/solver/test_sode_method.py @@ -4,7 +4,7 @@ from qutip import qeye, destroy, QobjEvo, rand_ket, rand_herm, create, Qobj, operator_to_vector, fock_dm import qutip.solver.sode._sode as _sode import pytest -from qutip.solver.sode.ssystem import SimpleStochasticSystem, StochasticOpenSystem +from qutip.solver.sode.ssystem import SimpleStochasticSystem, StochasticOpenSystem, StochasticClosedSystem from qutip.solver.sode.noise import _Noise from qutip.solver.stochastic import SMESolver, StochasticRHS @@ -126,7 +126,7 @@ def get_error_order_integrator(integrator, ref_integrator, state, plot=False): pytest.param("herm td", ["random"], ["destroy"], id='H td'), pytest.param("herm", ["random"], ["destroy td"], id='sc_ops td'), ]) -def test_integrator(method, order, H, c_ops, sc_ops): +def test_open_integrator(method, order, H, c_ops, sc_ops): N = 5 H = _make_oper(H, N) c_ops = [_make_oper(op, N) for op in c_ops] @@ -139,3 +139,29 @@ def test_integrator(method, order, H, c_ops, sc_ops): error_order = get_error_order_integrator(sode, ref_sode, state) assert (order + 0.35) < error_order + + +@pytest.mark.parametrize(["method", "order"], [ + pytest.param("euler", 0.5, id="Euler"), + pytest.param("platen", 1.0, id="Platen"), + pytest.param("rouchon", 1.0, id="Rouchon"), +]) +@pytest.mark.parametrize(['H', 'sc_ops'], [ + pytest.param("qeye", ["destroy"], id='simple'), + pytest.param("herm", ["destroy", "destroy2"], id='2 sc_ops'), + pytest.param("herm", ["random"], id='random'), + pytest.param("herm td", ["destroy"], id='H td'), + pytest.param("herm", ["destroy td"], id='sc_ops td'), +]) +def test_closed_integrator(method, order, H, sc_ops): + N = 5 + H = _make_oper(H, N) + sc_ops = [_make_oper(op, N) for op in sc_ops] + + rhs = StochasticRHS(StochasticClosedSystem, H, sc_ops, (), False) + ref_sode = SMESolver.avail_integrators()["explicit1.5"](rhs, {"dt": 0.01}) + sode = SMESolver.avail_integrators()[method](rhs, {"dt": 0.01}) + state = operator_to_vector(fock_dm(5, 3, dtype="Dense")).data + + error_order = get_error_order_integrator(sode, ref_sode, state) + assert (order + 0.35) < error_order diff --git a/qutip/tests/solver/test_stochastic_system.py b/qutip/tests/solver/test_stochastic_system.py index ff1fc0ff26..bd74c31885 100644 --- a/qutip/tests/solver/test_stochastic_system.py +++ b/qutip/tests/solver/test_stochastic_system.py @@ -106,7 +106,7 @@ def _check_equivalence(f, target, args): assert power > 0.9 -def _run_derr_check(solver, state, all): +def _run_derr_check(solver, state): """ """ @@ -124,10 +124,6 @@ def _run_derr_check(solver, state, all): L(solver, j, b), solver.Libj(j, i), (t, state) ) - if not all: - # Most method only need the Libj term. - return - _check_equivalence(L0(solver, a), solver.L0a(), (t, state)) for i in range(N): @@ -174,21 +170,4 @@ def test_open_system(H, c_ops, heterodyne): H = liouvillian(H) system = StochasticOpenSystem(H, c_ops, heterodyne=heterodyne) state = operator_to_vector(fock_dm(N, N-2)).data - _run_derr_check(system, state, True) - - -@pytest.mark.parametrize(['H', 'c_ops'], [ - pytest.param("qeye", ["destroy"], id='simple'), - pytest.param("tridiag", ["destroy"], id='tridiag'), - pytest.param("td", ["destroy"], id='H td'), - pytest.param("qeye", ["td"], id='c_ops td'), - pytest.param("rand", ["rand"], id='random'), -]) -def test_closed_system(H, c_ops): - N = 5 - H = _make_oper(H, N) - c_ops = [_make_oper(op, N) for op in c_ops] - system = StochasticClosedSystem(H, c_ops, heterodyne=False) - state = Qobj([1]*N).unit().data - all = False - _run_derr_check(system, state, False) + _run_derr_check(system, state) From c860893bb95406b1ca8efd0a1c6be6f73499e8c8 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Wed, 8 Feb 2023 14:13:22 +0900 Subject: [PATCH 094/602] Updated __all__ --- qutip/solver/result.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/solver/result.py b/qutip/solver/result.py index 087429230f..0cf5494b50 100644 --- a/qutip/solver/result.py +++ b/qutip/solver/result.py @@ -2,7 +2,7 @@ import numpy as np from ..core import Qobj, QobjEvo, expect, qzero -__all__ = ["Result", "MultiTrajResult", "McResult"] +__all__ = ["Result", "MultiTrajResult", "McResult", "NmmcResult"] class _QobjExpectEop: From 6ebe0e20e6cd439f40b011e00867b74589cb853f Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 24 Feb 2023 17:01:46 +0900 Subject: [PATCH 095/602] Added nm_mcsolve function associated with NonMarkovianMCSolver class --- qutip/solver/nm_mcsolve.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index b9afffc558..d759299682 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -5,7 +5,7 @@ import scipy from .mcsolve import MCSolver from .result import Result, NmmcResult -from ..core import CoreOptions, QobjEvo, isket, ket2dm, qeye +from ..core import CoreOptions, QobjEvo, isket, ket2dm, mesolve, MESolver, qeye def _2dm(q): @@ -14,6 +14,27 @@ def _2dm(q): return q +def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, + options=None, seeds=None, target_tol=None, timeout=None): + if not isinstance(ops_and_rates, (list, tuple)): + ops_and_rates = [ops_and_rates] + + if len(ops_and_rates) == 0: + if options is None: + options = {} + options = { + key: options[key] + for key in options + if key in MESolver.solver_options + } + return mesolve(H, state, tlist, e_ops=e_ops, options=options) + + nmmc = NonMarkovianMCSolver(H, ops_and_rates, options=options) + result = nmmc.run(state, tlist=tlist, ntraj=ntraj, e_ops=e_ops, + seed=seeds, target_tol=target_tol, timeout=timeout) + return result + + class NonMarkovianMCSolver(MCSolver): r""" Monte-Carlo solver for master equation with negative rates. From 44999f08f7d0785f8659c995b78d1a3435b19de4 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Sat, 25 Feb 2023 07:10:18 +0900 Subject: [PATCH 096/602] Bugfix: wrong import --- qutip/solver/nm_mcsolve.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index d759299682..802a1c7154 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -4,8 +4,9 @@ import numpy as np import scipy from .mcsolve import MCSolver +from .mesolve import MESolver, mesolve from .result import Result, NmmcResult -from ..core import CoreOptions, QobjEvo, isket, ket2dm, mesolve, MESolver, qeye +from ..core import CoreOptions, QobjEvo, isket, ket2dm, qeye def _2dm(q): From faf8ccdb87415be27aa146c06dc55f0954ae04b9 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Sat, 25 Feb 2023 09:51:45 +0900 Subject: [PATCH 097/602] Added Users Guide for nm_mcsolve based on documentation provided by Brecht Donvil --- doc/apidoc/functions.rst | 3 + doc/biblio.rst | 3 + doc/guide/dynamics/dynamics-monte.rst | 129 ++++++++++++++++++++++++-- qutip/solver/nm_mcsolve.py | 7 +- qutip/solver/result.py | 3 + 5 files changed, 136 insertions(+), 9 deletions(-) diff --git a/doc/apidoc/functions.rst b/doc/apidoc/functions.rst index 78a06941b5..bdfc97df6e 100644 --- a/doc/apidoc/functions.rst +++ b/doc/apidoc/functions.rst @@ -144,6 +144,9 @@ Monte Carlo Evolution .. automodule:: qutip.solver.mcsolve :members: mcsolve +.. automodule:: qutip.solver.nm_mcsolve + :members: nm_mcsolve + Krylov Subspace Solver ---------------------- diff --git a/doc/biblio.rst b/doc/biblio.rst index 7d3de972c4..b19a97ce94 100644 --- a/doc/biblio.rst +++ b/doc/biblio.rst @@ -71,3 +71,6 @@ Bibliography .. [NKanej] N Khaneja et. al. *Optimal control of coupled spin dynamics: Design of NMR pulse sequences by gradient ascent algorithms.* J. Magn. Reson. **172**, 296–305 (2005). :doi:`10.1016/j.jmr.2004.11.004` + +.. [Donvil22] + B. Donvil, P. Muratore-Ginanneschi, *Quantum trajectory framework for general time-local master equations*, Nat Commun **13**, 4140 (2022). :doi:`10.1038/s41467-022-31533-8`. \ No newline at end of file diff --git a/doc/guide/dynamics/dynamics-monte.rst b/doc/guide/dynamics/dynamics-monte.rst index 9362bffa92..f0a82e16db 100644 --- a/doc/guide/dynamics/dynamics-monte.rst +++ b/doc/guide/dynamics/dynamics-monte.rst @@ -13,30 +13,30 @@ Introduction Where as the density matrix formalism describes the ensemble average over many identical realizations of a quantum system, the Monte Carlo (MC), or quantum-jump approach to wave function evolution, allows for simulating an individual realization of the system dynamics. Here, the environment is continuously monitored, resulting in a series of quantum jumps in the system wave function, conditioned on the increase in information gained about the state of the system via the environmental measurements. In general, this evolution is governed by the Schrödinger equation with a **non-Hermitian** effective Hamiltonian .. math:: - :label: heff + :label: heff - H_{\rm eff}=H_{\rm sys}-\frac{i\hbar}{2}\sum_{i}C^{+}_{n}C_{n}, + H_{\rm eff}=H_{\rm sys}-\frac{i\hbar}{2}\sum_{i}C^{+}_{n}C_{n}, where again, the :math:`C_{n}` are collapse operators, each corresponding to a separate irreversible process with rate :math:`\gamma_{n}`. Here, the strictly negative non-Hermitian portion of Eq. :eq:`heff` gives rise to a reduction in the norm of the wave function, that to first-order in a small time :math:`\delta t`, is given by :math:`\left<\psi(t+\delta t)|\psi(t+\delta t)\right>=1-\delta p` where .. math:: - :label: jump + :label: jump - \delta p =\delta t \sum_{n}\left<\psi(t)|C^{+}_{n}C_{n}|\psi(t)\right>, + \delta p =\delta t \sum_{n}\left<\psi(t)|C^{+}_{n}C_{n}|\psi(t)\right>, and :math:`\delta t` is such that :math:`\delta p \ll 1`. With a probability of remaining in the state :math:`\left|\psi(t+\delta t)\right>` given by :math:`1-\delta p`, the corresponding quantum jump probability is thus Eq. :eq:`jump`. If the environmental measurements register a quantum jump, say via the emission of a photon into the environment, or a change in the spin of a quantum dot, the wave function undergoes a jump into a state defined by projecting :math:`\left|\psi(t)\right>` using the collapse operator :math:`C_{n}` corresponding to the measurement .. math:: - :label: project + :label: project - \left|\psi(t+\delta t)\right>=C_{n}\left|\psi(t)\right>/\left<\psi(t)|C_{n}^{+}C_{n}|\psi(t)\right>^{1/2}. + \left|\psi(t+\delta t)\right>=C_{n}\left|\psi(t)\right>/\left<\psi(t)|C_{n}^{+}C_{n}|\psi(t)\right>^{1/2}. If more than a single collapse operator is present in Eq. :eq:`heff`, the probability of collapse due to the :math:`i\mathrm{th}`-operator :math:`C_{i}` is given by .. math:: - :label: pcn + :label: pcn - P_{i}(t)=\left<\psi(t)|C_{i}^{+}C_{i}|\psi(t)\right>/\delta p. + P_{i}(t)=\left<\psi(t)|C_{i}^{+}C_{i}|\psi(t)\right>/\delta p. Evaluating the MC evolution to first-order in time is quite tedious. Instead, QuTiP uses the following algorithm to simulate a single realization of a quantum system. Starting from a pure state :math:`\left|\psi(0)\right>`: @@ -236,6 +236,119 @@ For example, the following code block plots expectation values for 1, 10 and 100 plt.legend() plt.show() + + +.. _monte-nonmarkov: + +Monte Carlo for Non-Markovian Dynamics +-------------------------------------- + +The Monte Carlo solver of QuTiP can also be used to solve the dynamics of time-local non-Markovian master equations, i.e., master equations of the Lindblad form + +.. math:: + :label: lindblad_master_equation_with_rates + + \dot\rho(t) = -\frac{i}{\hbar} [H, \rho(t)] + \sum_n \frac{\gamma_n(t)}{2} \left[2 A_n \rho(t) A_n^\dagger - \rho(t) A_n^\dagger A_n - A_n^\dagger A_n \rho(t)\right] + +with "rates" :math:`\gamma_n(t)` that can take negative values. +This can be done with the :func:`qutip.nm_mcsolve` function. +The function is based on the influence martingale formalism [Donvil22]_ and formally requires that the collapse operators :math:`A_n` satisfy a completeness relation of the form + +.. math:: + :label: nmmcsolve_completeness + + \sum_n A_n^\dagger A_n = \alpha \mathbb{I} , + +where :math:`\mathbb{I}` is the identity operator on the system Hilbert space and :math:`\alpha>0`. +Note that when the collapse operators of a model don't satisfy such a relation, ``qutip.nm_mcsolve`` automatically adds an extra collapse operator such that :eq:`nmmcsolve_completeness` is satisfied. +The rate corresponding to this extra collapse operator is set to zero. + +Technically, the influence martingale formalism works as follows. +We introduce an influence martingale :math:`\mu(t)`, which follows the evolution of the system state. +When no jump happens, it evolves as + +.. math:: + :label: influence_cont + + \mu(t) = \exp\left( \alpha\int_0^t K(\tau) d\tau \right) + +where :math:`K(t)` is for now an arbitrary function. +When a jump corresponding to the collapse operator :math:`A_n` happens, the influence martingale becomes + +.. math:: + :label: influence_disc + + \mu(t+\delta t) = \mu(t)\left(\frac{K(t)-\gamma_n(t)}{\gamma_n(t)}\right) + +Assuming that the state :math:`\bar\rho(t)` computed by the Monte Carlo average + +.. math:: + :label: mc_paired_state + + \bar\rho(t) = \frac{1}{N}\sum_{l=1}^N |\psi_l(t)\rangle\langle \psi_l(t)| + +solves a Lindblad master equation with collapse operators :math:`A_n` and rates :math:`\Gamma_n(t)`, the state :math:`\rho(t)` defined by + +.. math:: + :label: mc_martingale_state + + \rho(t) = \frac{1}{N}\sum_{l=1}^N \mu_l(t) |\psi_l(t)\rangle\langle \psi_l(t)| + +solves a Lindblad master equation with collapse operators :math:`A_n` and shifted rates :math:`\gamma_n(t)-K(t)`. +Thus, while :math:`\Gamma_n(t) \geq 0`, the new "rates" :math:`\gamma_n(t) = \Gamma_n(t) - K(t)` satisfy no positivity requirement. + +The input of :func:`qutip.nm_mcsolve` is almost the same as for :func:`qutip.mcsolve`. +The only difference is how the collapse operators and rate functions should be defined. +``nm_mcsolve`` requires collapse operators :math:`A_n` and target "rates" :math:`\gamma_n` (which are allowed to take negative values) to be given in list form ``[[C_1, gamma_1], [C_2, gamma_2], ...]``. +Note that we give the actual rate and not its square root, and that ``nm_mcsolve`` automatically computes associated jump rates :math:`\Gamma_n(t)\geq0` appropriate for simulation. + +We conclude with a simple example demonstrating the usage of the ``nm_mcsolve`` function. +For more elaborate, physically motivated examples, we refer to example notebook 1 [TODO] and example notebook 2 [TODO]. + + +.. plot:: + :context: reset + + import qutip as qt + import numpy as np + + times = np.linspace(0, 1, 201) + psi0 = qt.basis(2, 1) + a0 = qt.destroy(2) + H = a0.dag() * a0 + + # Rate functions + kappa = 1.0 / 0.129 + nth = 0.063 + def gamma1(t): + return kappa * nth + def gamma2(t): # becomes negative at times + return kappa * (nth+1) + 12 * np.exp(-2*t**3) * (-np.sin(15*t)**2) + + # nm_mcsolve integration + ops_and_rates = [] + ops_and_rates.append( [a0.dag(), gamma1] ) + ops_and_rates.append( [a0, gamma2] ) + MCSol = qt.nm_mcsolve(H, psi0, times, ops_and_rates, + e_ops=[a0.dag() * a0, a0 * a0.dag()], ntraj=2500) + + # mesolve integration for comparison + d_ops = [[qt.lindblad_dissipator(a0.dag(), a0.dag()), gamma1], + [qt.lindblad_dissipator(a0, a0), gamma2]] + MESol = qt.mesolve(H, psi0, times, d_ops, e_ops=[a0.dag() * a0, a0 * a0.dag()]) + + plt.figure() + plt.plot(times, MCSol.expect[0], 'g', times, MCSol.expect[1], 'b', times, MCSol.trace, 'r') + plt.plot(times, MESol.expect[0], 'g--', times, MESol.expect[1], 'b--') + plt.title('Monte Carlo time evolution') + plt.xlabel('Time') + plt.ylabel('Expectation values') + plt.legend((r'$\langle 1 | \rho | 1 \rangle$', + r'$\langle 0 | \rho | 0 \rangle$', + r'$\operatorname{tr} \rho$')) + plt.show() + + .. plot:: :context: reset :include-source: false diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 802a1c7154..a3bbf47ba0 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -1,4 +1,4 @@ -__all__ = ['NonMarkovianMCSolver'] +__all__ = ['nm_mcsolve', 'NonMarkovianMCSolver'] from functools import partial import numpy as np @@ -17,6 +17,9 @@ def _2dm(q): def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, options=None, seeds=None, target_tol=None, timeout=None): + r""" + TODO + """ if not isinstance(ops_and_rates, (list, tuple)): ops_and_rates = [ops_and_rates] @@ -40,6 +43,8 @@ class NonMarkovianMCSolver(MCSolver): r""" Monte-Carlo solver for master equation with negative rates. Based on the methods explained in arXiv:2209.08958 [quant-ph] + + TODO """ name = "nm_mcsolve" resultclass = NmmcResult diff --git a/qutip/solver/result.py b/qutip/solver/result.py index 0cf5494b50..4043f5c7f5 100644 --- a/qutip/solver/result.py +++ b/qutip/solver/result.py @@ -973,6 +973,9 @@ def runs_photocurrent(self): class NmmcResult(McResult): + r""" + TODO + """ def _add_trace(self, trajectory): new_trace = np.array(trajectory.trace) self._sum_trace += new_trace From 7127405d62e47b871986fcb41b0941a6aac9f9d0 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 27 Feb 2023 15:32:42 -0500 Subject: [PATCH 098/602] opt --- qutip/solve/_stochastic.pyx | 2 +- qutip/solver/sode/ssystem.pyx | 86 ++++++++++++++++++++--------------- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/qutip/solve/_stochastic.pyx b/qutip/solve/_stochastic.pyx index c2cac5416c..a13152e292 100644 --- a/qutip/solve/_stochastic.pyx +++ b/qutip/solve/_stochastic.pyx @@ -537,7 +537,7 @@ cdef class StochasticSolver: return states_list, noise, measurements, expect @cython.boundscheck(False) - cdef complex[::1] run(self, double t, double dt, double[:, ::1] noise, + cpdef complex[::1] run(self, double t, double dt, double[:, ::1] noise, complex[::1] vec, int num_substeps): """ Do one time full step""" cdef complex[::1] out = np.zeros(self.l_vec, dtype=complex) diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index e2d716a9ab..0f1f71a080 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -5,7 +5,8 @@ Class to represent a stochastic differential equation system. from qutip.core import data as _data from qutip.core.cy.qobjevo cimport QobjEvo -from qutip.core.data cimport Data, dense, Dense +from qutip.core.data cimport Data, dense, Dense, imul_dense, iadd_dense +from qutip.core.data.trace cimport trace_oper_ket_dense cimport cython import numpy as np from qutip.core import spre, spost, liouvillian @@ -132,6 +133,7 @@ cdef class StochasticClosedSystem(_StochasticSystem): out.append(_data.add(_out, state, -0.5 * expect)) return out + cdef class StochasticOpenSystem(_StochasticSystem): cdef QobjEvo L cdef list c_ops @@ -140,11 +142,11 @@ cdef class StochasticOpenSystem(_StochasticSystem): cdef int _is_set cdef bint _a_set, _b_set, _Lb_set, _L0b_set, _La_set, _LLb_set, _L0a_set - cdef readonly Dense _a, temp, _L0a - cdef readonly complex[::1] expect_Cv - cdef readonly complex[:, ::1] expect_Cb, _b, _La, _L0b - cdef readonly complex[:, :, ::1] _Lb - cdef readonly complex[:, :, :, ::1] _LLb + cdef Dense _a, temp, _L0a + cdef complex[::1] expect_Cv + cdef complex[:, ::1] expect_Cb, _b, _La, _L0b + cdef complex[:, :, ::1] _Lb + cdef complex[:, :, :, ::1] _LLb def __init__(self, H, sc_ops, c_ops=(), heterodyne=False): if H.issuper: @@ -217,10 +219,10 @@ cdef class StochasticOpenSystem(_StochasticSystem): self._compute_a() return self._a - cdef void _compute_a(self) except *: + cdef void _compute_a(StochasticOpenSystem self) except *: if not self._is_set: raise RuntimeError - _data.imul_dense(self._a, 0) + imul_dense(self._a, 0) self.L.matmul_data(self.t, self.state, self._a) self._a_set = True @@ -229,6 +231,8 @@ cdef class StochasticOpenSystem(_StochasticSystem): self._compute_b() return _dense_wrap(self._b[i, :]) + @cython.boundscheck(False) + @cython.wraparound(False) cdef void _compute_b(self) except *: if not self._is_set: raise RuntimeError @@ -238,10 +242,10 @@ cdef class StochasticOpenSystem(_StochasticSystem): for i in range(self.num_collapse): c_op = self.c_ops[i] b_vec = _dense_wrap(self._b[i, :]) - _data.imul_dense(b_vec, 0) + imul_dense(b_vec, 0) c_op.matmul_data(self.t, self.state, b_vec) - self.expect_Cv[i] = _data.trace_oper_ket_dense(b_vec) - _data.iadd_dense(b_vec, self.state, -self.expect_Cv[i]) + self.expect_Cv[i] = trace_oper_ket_dense(b_vec) + iadd_dense(b_vec, self.state, -self.expect_Cv[i]) self._b_set = True cpdef Data Libj(self, int i, int j): @@ -252,6 +256,8 @@ cdef class StochasticOpenSystem(_StochasticSystem): j, i = i, j return _dense_wrap(self._Lb[i, j, :]) + @cython.boundscheck(False) + @cython.wraparound(False) cdef void _compute_Lb(self) except *: cdef int i, j cdef QobjEvo c_op @@ -265,11 +271,11 @@ cdef class StochasticOpenSystem(_StochasticSystem): for j in range(i, self.num_collapse): b_vec = _dense_wrap(self._b[j, :]) Lb_vec = _dense_wrap(self._Lb[i, j, :]) - _data.imul_dense(Lb_vec, 0) + imul_dense(Lb_vec, 0) c_op.matmul_data(self.t, b_vec, Lb_vec) - self.expect_Cb[i,j] = _data.trace_oper_ket_dense(Lb_vec) - _data.iadd_dense(Lb_vec, b_vec, -self.expect_Cv[i]) - _data.iadd_dense(Lb_vec, self.state, -self.expect_Cb[i,j]) + self.expect_Cb[i,j] = trace_oper_ket_dense(Lb_vec) + iadd_dense(Lb_vec, b_vec, -self.expect_Cv[i]) + iadd_dense(Lb_vec, self.state, -self.expect_Cb[i,j]) self._Lb_set = True cpdef Data Lia(self, int i): @@ -277,6 +283,8 @@ cdef class StochasticOpenSystem(_StochasticSystem): self._compute_La() return _dense_wrap(self._La[i, :]) + @cython.boundscheck(False) + @cython.wraparound(False) cdef void _compute_La(self) except *: cdef int i cdef QobjEvo c_op @@ -287,7 +295,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): for i in range(self.num_collapse): b_vec = _dense_wrap(self._b[i, :]) La_vec = _dense_wrap(self._La[i, :]) - _data.imul_dense(La_vec, 0.) + imul_dense(La_vec, 0.) self.L.matmul_data(self.t, b_vec, La_vec) self._La_set = True @@ -297,6 +305,8 @@ cdef class StochasticOpenSystem(_StochasticSystem): self._compute_L0b() return _dense_wrap(self._L0b[i, :]) + @cython.boundscheck(False) + @cython.wraparound(False) cdef void _compute_L0b(self) except *: cdef int i, j cdef QobjEvo c_op @@ -310,30 +320,30 @@ cdef class StochasticOpenSystem(_StochasticSystem): c_op = self.c_ops[i] L0b_vec = _dense_wrap(self._L0b[i, :]) b_vec = _dense_wrap(self._b[i, :]) - _data.imul_dense(L0b_vec, 0.) + imul_dense(L0b_vec, 0.) # db/dt c_op.matmul_data(self.t + self.dt, self.state, L0b_vec) - expect = _data.trace_oper_ket_dense(L0b_vec) - _data.iadd_dense(L0b_vec, self.state, -expect) - _data.iadd_dense(L0b_vec, b_vec, -1) - _data.imul_dense(L0b_vec, 1/self.dt) + expect = trace_oper_ket_dense(L0b_vec) + iadd_dense(L0b_vec, self.state, -expect) + iadd_dense(L0b_vec, b_vec, -1) + imul_dense(L0b_vec, 1/self.dt) # ab' - _data.imul_dense(self.temp, 0) + imul_dense(self.temp, 0) c_op.matmul_data(self.t, self._a, self.temp) - expect = _data.trace_oper_ket_dense(self.temp) - _data.iadd_dense(L0b_vec, self.temp, 1) - _data.iadd_dense(L0b_vec, self._a, -self.expect_Cv[i]) - _data.iadd_dense(L0b_vec, self.state, -expect) + expect = trace_oper_ket_dense(self.temp) + iadd_dense(L0b_vec, self.temp, 1) + iadd_dense(L0b_vec, self._a, -self.expect_Cv[i]) + iadd_dense(L0b_vec, self.state, -expect) # bbb" : expect_Cb[i,j] only defined for j>=i for j in range(i): b_vec = _dense_wrap(self._b[j, :]) - _data.iadd_dense(L0b_vec, b_vec, -self.expect_Cb[j,i]) + iadd_dense(L0b_vec, b_vec, -self.expect_Cb[j,i]) for j in range(i, self.num_collapse): b_vec = _dense_wrap(self._b[j, :]) - _data.iadd_dense(L0b_vec, b_vec, -self.expect_Cb[i,j]) + iadd_dense(L0b_vec, b_vec, -self.expect_Cb[i,j]) self._L0b_set = True cpdef Data LiLjbk(self, int i, int j, int k): @@ -350,6 +360,8 @@ cdef class StochasticOpenSystem(_StochasticSystem): return _dense_wrap(self._LLb[i, j, k, :]) + @cython.boundscheck(False) + @cython.wraparound(False) cdef void _compute_LLb(self) except *: # LiLjbk = bi(bj'bk'+bjbk"), i<=j<=k # sc_ops must commute (LiLjbk = LjLibk = LkLjbi) @@ -367,15 +379,15 @@ cdef class StochasticOpenSystem(_StochasticSystem): Lb_vec = _dense_wrap(self._Lb[j, k, :]) bj_vec = _dense_wrap(self._b[j, :]) bk_vec = _dense_wrap(self._b[k, :]) - _data.imul_dense(LLb_vec, 0.) + imul_dense(LLb_vec, 0.) c_op.matmul_data(self.t, Lb_vec, LLb_vec) - expect = _data.trace_oper_ket_dense(LLb_vec) + expect = trace_oper_ket_dense(LLb_vec) - _data.iadd_dense(LLb_vec, Lb_vec, -self.expect_Cv[i]) - _data.iadd_dense(LLb_vec, self.state, -expect) - _data.iadd_dense(LLb_vec, bj_vec, -self.expect_Cb[i,k]) - _data.iadd_dense(LLb_vec, bk_vec, -self.expect_Cb[i,j]) + iadd_dense(LLb_vec, Lb_vec, -self.expect_Cv[i]) + iadd_dense(LLb_vec, self.state, -expect) + iadd_dense(LLb_vec, bj_vec, -self.expect_Cb[i,k]) + iadd_dense(LLb_vec, bk_vec, -self.expect_Cb[i,j]) self._LLb_set = True @@ -387,10 +399,10 @@ cdef class StochasticOpenSystem(_StochasticSystem): cdef void _compute_L0a(self) except *: # L0a = a'a + da/dt + bba"/2 (a" = 0) - _data.imul_dense(self._L0a, 0.) + imul_dense(self._L0a, 0.) self.L.matmul_data(self.t + self.dt, self.state, self._L0a) - _data.iadd_dense(self._L0a, self._a, -1) - _data.imul_dense(self._L0a, 1/self.dt) + iadd_dense(self._L0a, self._a, -1) + imul_dense(self._L0a, 1/self.dt) self.L.matmul_data(self.t, self._a, self._L0a) self._L0a_set = True From c23e25c27b929e6bf074a3d5b421ac13af6830a4 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 28 Feb 2023 13:54:35 +0900 Subject: [PATCH 099/602] Renamed trajectoryclass -> trajectory_resultclass --- qutip/solver/mcsolve.py | 4 ++-- qutip/solver/multitraj.py | 4 ++-- qutip/solver/nm_mcsolve.py | 10 ++++++---- qutip/solver/result.py | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 48150c4555..6f99e79c03 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -4,7 +4,7 @@ from ..core import QobjEvo, spre, spost, Qobj, unstack_columns from .multitraj import MultiTrajSolver from .solver_base import Solver, Integrator -from .result import McResult, McTrajectory +from .result import McResult, McTrajectoryResult from .mesolve import mesolve, MESolver import qutip.core.data as _data from time import time @@ -347,7 +347,7 @@ class MCSolver(MultiTrajSolver): """ name = "mcsolve" resultclass = McResult - trajectoryclass = McTrajectory + trajectory_resultclass = McTrajectoryResult solver_options = { "progress_bar": "text", "progress_kwargs": {"chunk_size": 10}, diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 08f49b1494..de06a19325 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -28,7 +28,7 @@ class MultiTrajSolver(Solver): """ name = "generic multi trajectory" resultclass = MultiTrajResult - trajectoryclass = Result + trajectory_resultclass = Result _avail_integrators = {} # Class of option used by the solver @@ -197,7 +197,7 @@ def _run_one_traj(self, seed, state, tlist, e_ops): """ Run one trajectory and return the result. """ - result = self.trajectoryclass(e_ops, self.options) + result = self.trajectory_resultclass(e_ops, self.options) generator = self._get_generator(seed) self._integrator.set_state(tlist[0], state, generator) result.add(tlist[0], self._restore_state(state, copy=False)) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index c0e7646f04..d547d4e86b 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -4,7 +4,7 @@ import numpy as np import scipy from .mcsolve import MCSolver -from .result import NmmcResult, NmmcTrajectory +from .result import NmmcResult, NmmcTrajectoryResult from ..core import CoreOptions, QobjEvo, isket, ket2dm, qeye @@ -15,18 +15,20 @@ class NonMarkovianMCSolver(MCSolver): """ name = "nm_mcsolve" resultclass = NmmcResult - # "solver" argument will be partially initialized in constructor - trajectoryclass = NmmcTrajectory solver_options = { **MCSolver.solver_options, "completeness_rtol": 1e-5, "completeness_atol": 1e-8, } + # "solver" argument will be partially initialized in constructor + trajectory_resultclass = NmmcTrajectoryResult + # ops_and_rates is a list of tuples (L_i, Gamma_i), # where Gamma_i = Gamma_i(t) is callable def __init__(self, H, ops_and_rates, *args, options=None, **kwargs): - self.trajectoryclass = partial(NmmcTrajectory, solver=self) + self.trajectory_resultclass = partial(NmmcTrajectoryResult, + solver=self) self.ops_and_rates = list(ops_and_rates) self.options = options diff --git a/qutip/solver/result.py b/qutip/solver/result.py index 8d94bbdb52..e11ad96735 100644 --- a/qutip/solver/result.py +++ b/qutip/solver/result.py @@ -866,7 +866,7 @@ def __add__(self, other): return new -class McTrajectory(Result): +class McTrajectoryResult(Result): def __init__(self, e_ops, options, *args, **kwargs): super().__init__(e_ops, {**options, "normalize_output": False}, *args, **kwargs) @@ -978,7 +978,7 @@ def runs_photocurrent(self): return measurements -class NmmcTrajectory(McTrajectory): +class NmmcTrajectoryResult(McTrajectoryResult): def __init__(self, e_ops, options, solver, *args, **kwargs): super().__init__(e_ops, options, *args, **kwargs) From e3812191119c15c311c4811aa2c501c3b7796125 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Wed, 1 Mar 2023 01:45:19 +0530 Subject: [PATCH 100/602] added __all__ to file, fix #2101 --- qutip/core/semidefinite.py | 17 +++++++++++++++++ qutip/measurement.py | 9 +++++++++ 2 files changed, 26 insertions(+) diff --git a/qutip/core/semidefinite.py b/qutip/core/semidefinite.py index ba96aeec23..0c5774fd51 100644 --- a/qutip/core/semidefinite.py +++ b/qutip/core/semidefinite.py @@ -4,6 +4,23 @@ This module implements internal-use functions for semidefinite programming. """ +__all__ = [ + 'complex_var', + 'herm', + 'pos_noherm', + 'pos', + 'dens', + 'kron', + 'conj', + 'bmat', + 'dag', + 'memoize', + 'qudit_swap', + 'initialize_constraints_on_dnorm_problem', + 'dnorm_problem', + 'dnorm_sparse_problem', +] + import collections import functools diff --git a/qutip/measurement.py b/qutip/measurement.py index 0211180a73..da90387dd0 100644 --- a/qutip/measurement.py +++ b/qutip/measurement.py @@ -2,6 +2,15 @@ Module for measuring quantum objects. """ +__all__ = [ + 'measurement_statistics_povm', + 'measurement_statistics_observable', + 'measure_observable', + 'measure_povm', + 'measurement_statistics', + 'measure' +] + import numpy as np from . import Qobj, expect, identity, tensor From 7f13330130bc9506b849bdd6dc759b591c9cf0dc Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 28 Feb 2023 17:05:00 -0500 Subject: [PATCH 101/602] Optimized milstein --- qutip/core/data/mul.pyx | 18 +++--- qutip/solve/_stochastic.pyx | 2 +- qutip/solver/sode/_sode.pyx | 66 ++++++++++---------- qutip/solver/sode/ssystem.pxd | 3 +- qutip/solver/sode/ssystem.pyx | 31 +++++---- qutip/tests/solver/test_stochastic_system.py | 2 +- 6 files changed, 68 insertions(+), 54 deletions(-) diff --git a/qutip/core/data/mul.pyx b/qutip/core/data/mul.pyx index 27d8e47663..7de377fcf2 100644 --- a/qutip/core/data/mul.pyx +++ b/qutip/core/data/mul.pyx @@ -2,6 +2,13 @@ #cython: boundscheck=False, wrapround=False, initializedcheck=False from qutip.core.data cimport idxint, csr, CSR, dense, Dense, Data +from scipy.linalg.cython_blas cimport zscal + +cdef int ZERO=0 +cdef double DZERO=0 +cdef complex ZZERO=0j +cdef complex ZNEG=-1 +cdef int ONE=1 __all__ = [ 'mul', 'mul_csr', 'mul_dense', @@ -12,10 +19,8 @@ __all__ = [ cpdef CSR imul_csr(CSR matrix, double complex value): """Multiply this CSR `matrix` by a complex scalar `value`.""" - cdef idxint ptr - with nogil: - for ptr in range(csr.nnz(matrix)): - matrix.data[ptr] *= value + cdef idxint l = csr.nnz(matrix) + zscal(&l, &value, matrix.data, &ONE) return matrix cpdef CSR mul_csr(CSR matrix, double complex value): @@ -42,9 +47,8 @@ cpdef CSR neg_csr(CSR matrix): cpdef Dense imul_dense(Dense matrix, double complex value): """Multiply this Dense `matrix` by a complex scalar `value`.""" cdef size_t ptr - with nogil: - for ptr in range(matrix.shape[0]*matrix.shape[1]): - matrix.data[ptr] *= value + cdef idxint l = matrix.shape[0]*matrix.shape[1] + zscal(&l, &value, matrix.data, &ONE) return matrix cpdef Dense mul_dense(Dense matrix, double complex value): diff --git a/qutip/solve/_stochastic.pyx b/qutip/solve/_stochastic.pyx index a13152e292..ea1c7497ae 100644 --- a/qutip/solve/_stochastic.pyx +++ b/qutip/solve/_stochastic.pyx @@ -1657,7 +1657,7 @@ cdef class SMESolver(StochasticSolver): @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) - cdef complex expect(self, complex[::1] rho): + cpdef complex expect(self, complex[::1] rho): cdef complex e = 0. cdef int k for k in range(self.N_root): diff --git a/qutip/solver/sode/_sode.pyx b/qutip/solver/sode/_sode.pyx index d86102c448..884f15ef3d 100644 --- a/qutip/solver/sode/_sode.pyx +++ b/qutip/solver/sode/_sode.pyx @@ -2,7 +2,7 @@ from qutip.core import data as _data from qutip.core.cy.qobjevo cimport QobjEvo -from qutip.core.data cimport Data, Dense +from qutip.core.data cimport Data, Dense, imul_dense, iadd_dense from collections import defaultdict cimport cython from qutip.solver.sode.ssystem cimport _StochasticSystem @@ -18,11 +18,11 @@ cdef class Euler: @cython.wraparound(False) def run( self, double t, Data state, double dt, - double[:, :, ::1] dW, int ntraj + double[:, :, ::1] dW, int num_step ): cdef int i cdef Data out - for i in range(ntraj): + for i in range(num_step): state = self.step(t + i * dt, state, dt, dW[i, :, :]) return state @@ -260,12 +260,12 @@ cdef class Milstein: system.set_state(t, state) - _data.imul_dense(out, 0.) - _data.iadd_dense(out, state, 1) - _data.iadd_dense(out, system.a(), dt) + imul_dense(out, 0.) + iadd_dense(out, state, 1) + iadd_dense(out, system.a(), dt) for i in range(num_ops): - _data.iadd_dense(out, system.bi(i), dW[0, i]) + iadd_dense(out, system.bi(i), dW[0, i]) for i in range(num_ops): for j in range(i, num_ops): @@ -273,7 +273,7 @@ cdef class Milstein: dw = (dW[0, i] * dW[0, j] - dt) * 0.5 else: dw = dW[0, i] * dW[0, j] - _data.iadd_dense(out, system.Libj(i, j), dw) + iadd_dense(out, system.Libj(i, j), dw) cdef class PredCorr: @@ -318,26 +318,26 @@ cdef class PredCorr: system.set_state(t, state) - _data.imul_dense(out, 0.) - _data.iadd_dense(out, state, 1) - _data.iadd_dense(out, system.a(), dt * (1-alpha)) + imul_dense(out, 0.) + iadd_dense(out, state, 1) + iadd_dense(out, system.a(), dt * (1-alpha)) - _data.imul_dense(euler, 0.) - _data.iadd_dense(euler, state, 1) - _data.iadd_dense(euler, system.a(), dt) + imul_dense(euler, 0.) + iadd_dense(euler, state, 1) + iadd_dense(euler, system.a(), dt) for i in range(num_ops): - _data.iadd_dense(euler, system.bi(i), dW[0, i]) - _data.iadd_dense(out, system.bi(i), dW[0, i] * eta) - _data.iadd_dense(out, system.Libj(i, i), dt * (alpha-1) * 0.5) + iadd_dense(euler, system.bi(i), dW[0, i]) + iadd_dense(out, system.bi(i), dW[0, i] * eta) + iadd_dense(out, system.Libj(i, i), dt * (alpha-1) * 0.5) system.set_state(t+dt, euler) if alpha: - _data.iadd_dense(out, system.a(), dt*alpha) + iadd_dense(out, system.a(), dt*alpha) for i in range(num_ops): - _data.iadd_dense(out, system.bi(i), dW[0, i] * (1-eta)) - _data.iadd_dense(out, system.Libj(i, i), -dt * alpha * 0.5) + iadd_dense(out, system.bi(i), dW[0, i] * (1-eta)) + iadd_dense(out, system.Libj(i, i), -dt * alpha * 0.5) return out @@ -373,24 +373,24 @@ cdef class Taylor15(Milstein): dw = dW[0, :] dz = 0.5 * (dW[0, :] + dW[1, :] / np.sqrt(3)) * dt - _data.imul_dense(out, 0.) - _data.iadd_dense(out, state, 1) - _data.iadd_dense(out, system.a(), dt) - _data.iadd_dense(out, system.L0a(), 0.5 * dt * dt) + imul_dense(out, 0.) + iadd_dense(out, state, 1) + iadd_dense(out, system.a(), dt) + iadd_dense(out, system.L0a(), 0.5 * dt * dt) for i in range(num_ops): - _data.iadd_dense(out, system.bi(i), dw[i]) - _data.iadd_dense(out, system.Libj(i, i), 0.5 * (dw[i] * dw[i] - dt)) - _data.iadd_dense(out, system.Lia(i), dz[i]) - _data.iadd_dense(out, system.L0bi(i), dw[i] * dt - dz[i]) - _data.iadd_dense(out, system.LiLjbk(i, i, i), + iadd_dense(out, system.bi(i), dw[i]) + iadd_dense(out, system.Libj(i, i), 0.5 * (dw[i] * dw[i] - dt)) + iadd_dense(out, system.Lia(i), dz[i]) + iadd_dense(out, system.L0bi(i), dw[i] * dt - dz[i]) + iadd_dense(out, system.LiLjbk(i, i, i), 0.5 * ((1/3.) * dw[i] * dw[i] - dt) * dw[i]) for j in range(i+1, num_ops): - _data.iadd_dense(out, system.Libj(i, j), dw[i] * dw[j]) - _data.iadd_dense(out, system.LiLjbk(i, j, j), 0.5 * (dw[j] * dw[j] -dt) * dw[i]) - _data.iadd_dense(out, system.LiLjbk(i, i, j), 0.5 * (dw[i] * dw[i] -dt) * dw[j]) + iadd_dense(out, system.Libj(i, j), dw[i] * dw[j]) + iadd_dense(out, system.LiLjbk(i, j, j), 0.5 * (dw[j] * dw[j] -dt) * dw[i]) + iadd_dense(out, system.LiLjbk(i, i, j), 0.5 * (dw[i] * dw[i] -dt) * dw[j]) for k in range(j+1, num_ops): - _data.iadd_dense(out, system.LiLjbk(i, j, k), dw[i]*dw[j]*dw[k]) + iadd_dense(out, system.LiLjbk(i, j, k), dw[i]*dw[j]*dw[k]) return out diff --git a/qutip/solver/sode/ssystem.pxd b/qutip/solver/sode/ssystem.pxd index 77b2981ef1..8be43118a1 100644 --- a/qutip/solver/sode/ssystem.pxd +++ b/qutip/solver/sode/ssystem.pxd @@ -12,7 +12,8 @@ cdef class _StochasticSystem: cpdef list diffusion(self, t, Data state) - cpdef void set_state(self, double t, Data state) except * + cpdef void set_state(self, double t, Dense state) except * + cpdef Data a(self) cpdef Data bi(self, int i) cpdef Data Libj(self, int i, int j) diff --git a/qutip/solver/sode/ssystem.pyx b/qutip/solver/sode/ssystem.pyx index 0f1f71a080..c8f070f37c 100644 --- a/qutip/solver/sode/ssystem.pyx +++ b/qutip/solver/sode/ssystem.pyx @@ -31,7 +31,7 @@ cdef class _StochasticSystem: cpdef list diffusion(self, t, Data state): raise NotImplementedError - cpdef void set_state(self, double t, Data state) except *: + cpdef void set_state(self, double t, Dense state) except *: raise NotImplementedError cpdef Data a(self): @@ -137,7 +137,7 @@ cdef class StochasticClosedSystem(_StochasticSystem): cdef class StochasticOpenSystem(_StochasticSystem): cdef QobjEvo L cdef list c_ops - cdef int state_size + cdef int state_size, N_root cdef double dt cdef int _is_set cdef bint _a_set, _b_set, _Lb_set, _L0b_set, _La_set, _LLb_set, _L0a_set @@ -170,6 +170,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): self.dims = self.L.dims self.state_size = self.L.shape[1] self._is_set = 0 + self.N_root = self.state_size**0.5 cpdef Data drift(self, t, Data state): return self.L.matmul_data(t, state) @@ -186,10 +187,12 @@ cdef class StochasticOpenSystem(_StochasticSystem): out.append(_data.add(vec, state, -expect)) return out - cpdef void set_state(self, double t, Data state) except *: + cpdef void set_state(self, double t, Dense state) except *: cdef n, l self.t = t - self.state = _data.to(_data.Dense, state).reorder(fortran=1) + if not state.fortran: + state = state.reorder(fortran=1) + self.state = state self._a_set = False self._b_set = False self._Lb_set = False @@ -215,6 +218,8 @@ cdef class StochasticOpenSystem(_StochasticSystem): self.dt = 1e-6 # Make an options cpdef Data a(self): + if not self._is_set: + raise RuntimeError if not self._a_set: self._compute_a() return self._a @@ -227,6 +232,8 @@ cdef class StochasticOpenSystem(_StochasticSystem): self._a_set = True cpdef Data bi(self, int i): + if not self._is_set: + raise RuntimeError if not self._b_set: self._compute_b() return _dense_wrap(self._b[i, :]) @@ -238,17 +245,19 @@ cdef class StochasticOpenSystem(_StochasticSystem): raise RuntimeError cdef int i cdef QobjEvo c_op - cdef Dense b_vec + cdef Dense b_vec, state=self.state for i in range(self.num_collapse): c_op = self.c_ops[i] b_vec = _dense_wrap(self._b[i, :]) imul_dense(b_vec, 0) - c_op.matmul_data(self.t, self.state, b_vec) + c_op.matmul_data(self.t, state, b_vec) self.expect_Cv[i] = trace_oper_ket_dense(b_vec) - iadd_dense(b_vec, self.state, -self.expect_Cv[i]) + iadd_dense(b_vec, state, -self.expect_Cv[i]) self._b_set = True cpdef Data Libj(self, int i, int j): + if not self._is_set: + raise RuntimeError if not self._Lb_set: self._compute_Lb() # We only support commutative diffusion @@ -261,7 +270,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): cdef void _compute_Lb(self) except *: cdef int i, j cdef QobjEvo c_op - cdef Dense b_vec, Lb_vec + cdef Dense b_vec, Lb_vec, state=self.state cdef complex expect if not self._b_set: self._compute_b() @@ -275,7 +284,7 @@ cdef class StochasticOpenSystem(_StochasticSystem): c_op.matmul_data(self.t, b_vec, Lb_vec) self.expect_Cb[i,j] = trace_oper_ket_dense(Lb_vec) iadd_dense(Lb_vec, b_vec, -self.expect_Cv[i]) - iadd_dense(Lb_vec, self.state, -self.expect_Cb[i,j]) + iadd_dense(Lb_vec, state, -self.expect_Cb[i,j]) self._Lb_set = True cpdef Data Lia(self, int i): @@ -434,9 +443,9 @@ cdef class SimpleStochasticSystem(_StochasticSystem): out.append(self.c_ops[i].matmul_data(t, state)) return out - cpdef void set_state(self, double t, Data state) except *: + cpdef void set_state(self, double t, Dense state) except *: self.t = t - self.state = _data.to(_data.Dense, state) + self.state = state cpdef Data a(self): return self.H.matmul_data(self.t, self.state) diff --git a/qutip/tests/solver/test_stochastic_system.py b/qutip/tests/solver/test_stochastic_system.py index bd74c31885..c03c7112be 100644 --- a/qutip/tests/solver/test_stochastic_system.py +++ b/qutip/tests/solver/test_stochastic_system.py @@ -169,5 +169,5 @@ def test_open_system(H, c_ops, heterodyne): c_ops = [_make_oper(op, N) for op in c_ops] H = liouvillian(H) system = StochasticOpenSystem(H, c_ops, heterodyne=heterodyne) - state = operator_to_vector(fock_dm(N, N-2)).data + state = operator_to_vector(fock_dm(N, N-2, dtype="Dense")).data _run_derr_check(system, state) From 2c9496eb946f83298d2561e3a0627722cec1dc3e Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 1 Mar 2023 11:30:12 -0500 Subject: [PATCH 102/602] opt pred_corr --- qutip/solver/sode/_sode.pyx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qutip/solver/sode/_sode.pyx b/qutip/solver/sode/_sode.pyx index 884f15ef3d..b6ed449725 100644 --- a/qutip/solver/sode/_sode.pyx +++ b/qutip/solver/sode/_sode.pyx @@ -332,12 +332,13 @@ cdef class PredCorr: iadd_dense(out, system.Libj(i, i), dt * (alpha-1) * 0.5) system.set_state(t+dt, euler) - if alpha: - iadd_dense(out, system.a(), dt*alpha) - for i in range(num_ops): iadd_dense(out, system.bi(i), dW[0, i] * (1-eta)) - iadd_dense(out, system.Libj(i, i), -dt * alpha * 0.5) + + if alpha: + iadd_dense(out, system.a(), dt*alpha) + for i in range(num_ops): + iadd_dense(out, system.Libj(i, i), -dt * alpha * 0.5) return out From 6fb6d9672994ffba9d235266b2c3ace4c1fcfc45 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 1 Mar 2023 17:29:33 -0500 Subject: [PATCH 103/602] superop rouchon --- qutip/core/cy/qobjevo.pxd | 2 +- qutip/solver/sode/_sode.pyx | 56 ++++++++++++++++++++++-------------- qutip/solver/sode/rouchon.py | 41 ++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 23 deletions(-) diff --git a/qutip/core/cy/qobjevo.pxd b/qutip/core/cy/qobjevo.pxd index de310b6910..f138c89592 100644 --- a/qutip/core/cy/qobjevo.pxd +++ b/qutip/core/cy/qobjevo.pxd @@ -5,7 +5,7 @@ from qutip.core.data.base cimport idxint cdef class QobjEvo: cdef: - list elements + readonly list elements readonly list dims readonly (idxint, idxint) shape readonly str type diff --git a/qutip/solver/sode/_sode.pyx b/qutip/solver/sode/_sode.pyx index b6ed449725..c7b7f3ef11 100644 --- a/qutip/solver/sode/_sode.pyx +++ b/qutip/solver/sode/_sode.pyx @@ -67,7 +67,7 @@ cdef class Platen(Euler): cdef int i, j, num_ops = system.num_collapse cdef double sqrt_dt = np.sqrt(dt) cdef double sqrt_dt_inv = 0.25 / sqrt_dt - cdef double dw, dw2 + cdef double dw, dw2, dw2p, dw2m cdef Data d1 = _data.add(state, system.drift(t, state), dt) cdef list d2 = system.diffusion(t, state) @@ -90,14 +90,20 @@ cdef class Platen(Euler): d2p = system.diffusion(t, Vp[i]) d2m = system.diffusion(t, Vm[i]) dw = dW[0, i] * 0.25 - out = _data.add(out, d2m[i], dw) + # out = _data.add(out, d2m[i], dw) out = _data.add(out, d2[i], 2 * dw) - out = _data.add(out, d2p[i], dw) + # out = _data.add(out, d2p[i], dw) for j in range(num_ops): - dw2 = sqrt_dt_inv * (dW[0, i] * dW[0, j] - dt * (i == j)) - out = _data.add(out, d2p[j], dw2) - out = _data.add(out, d2m[j], -dw2) + if i == j: + dw2 = sqrt_dt_inv * (dW[0, i] * dW[0, j] - dt) + dw2p = dw2 + dw + dw2m = -dw2 + dw + else: + dw2p = sqrt_dt_inv * dW[0, i] * dW[0, j] + dw2m = -dw2p + out = _data.add(out, d2p[j], dw2p) + out = _data.add(out, d2m[j], dw2m) return out @@ -117,10 +123,12 @@ cdef class Explicit15(Euler): cdef double sqrt_dt = np.sqrt(dt) cdef double sqrt_dt_inv = 1./sqrt_dt cdef double ddz, ddw, ddd - cdef double[::1] dz, dw + cdef double[::1] dz, dw, dwp, dwm dw = np.empty(num_ops) dz = np.empty(num_ops) + dwp = np.zeros(num_ops) + dwm = np.zeros(num_ops) for i in range(num_ops): dw[i] = dW[0, i] dz[i] = 0.5 *(dW[0, i] + 1./np.sqrt(3) * dW[1, i]) @@ -162,6 +170,9 @@ cdef class Explicit15(Euler): for i in range(num_ops): ddz = dz[i] * 0.5 / sqrt_dt # 1.5 ddd = 0.25 * (dw[i] * dw[i] / 3 - dt) * dw[i] / dt # 1.5 + for j in range(num_ops): + dwp[j] = 0 + dwm[j] = 0 d1p = system.drift(t + dt/num_ops, v2p[i]) d1m = system.drift(t + dt/num_ops, v2m[i]) @@ -179,47 +190,48 @@ cdef class Explicit15(Euler): out = _data.add(out, d2pp[i], ddd) out = _data.add(out, d2mm[i], -ddd) - out = _data.add(out, d2p[i], -ddd) - out = _data.add(out, d2m[i], ddd) + dwp[i] += -ddd + dwm[i] += ddd for j in range(num_ops): ddw = 0.5 * (dw[j] - dz[j]) # O(1.5) - out = _data.add(out, d2p[j], ddw) + dwp[j] += ddw + dwm[j] += ddw out = _data.add(out, d2[j], -2*ddw) - out = _data.add(out, d2m[j], ddw) if j > i: ddw = 0.5 * (dw[i] * dw[j]) / sqrt_dt # O(1.0) - out = _data.add(out, d2p[j], ddw) - out = _data.add(out, d2m[j], -ddw) + dwp[j] += ddw + dwm[j] += -ddw ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt # O(1.5) d2pp = system.diffusion(t, p2p[j][i]) d2mm = system.diffusion(t, p2m[j][i]) - out = _data.add(out, d2pp[j], ddw) out = _data.add(out, d2mm[j], -ddw) - out = _data.add(out, d2p[j], -ddw) - out = _data.add(out, d2m[j], ddw) + dwp[j] += -ddw + dwm[j] += ddw for k in range(j+1, num_ops): ddw = 0.5 * dw[i] * dw[j] * dw[k] / dt # O(1.5) - out = _data.add(out, d2pp[k], ddw) out = _data.add(out, d2mm[k], -ddw) - out = _data.add(out, d2p[k], -ddw) - out = _data.add(out, d2m[k], ddw) + dwp[k] += -ddw + dwm[k] += ddw if j < i: ddw = 0.25 * (dw[j] * dw[j] - dt) * dw[i] / dt # O(1.5) - d2pp = system.diffusion(t, p2p[j][i]) d2mm = system.diffusion(t, p2m[j][i]) out = _data.add(out, d2pp[j], ddw) out = _data.add(out, d2mm[j], -ddw) - out = _data.add(out, d2p[j], -ddw) - out = _data.add(out, d2m[j], ddw) + dwp[j] += -ddw + dwm[j] += ddw + + for j in range(num_ops): + out = _data.add(out, d2p[j], dwp[j]) + out = _data.add(out, d2m[j], dwm[j]) return out diff --git a/qutip/solver/sode/rouchon.py b/qutip/solver/sode/rouchon.py index 03f9b98039..0b9801ef31 100644 --- a/qutip/solver/sode/rouchon.py +++ b/qutip/solver/sode/rouchon.py @@ -37,11 +37,14 @@ def __init__(self, rhs, options): self.c_ops = rhs.c_ops self.sc_ops = rhs.sc_ops self.cpcds = [op + op.dag() for op in self.sc_ops] + for op in self.cpcds: + op.compress() self.M = ( - 1j * self.H - sum(op.dag() @ op for op in self.c_ops) * 0.5 - sum(op.dag() @ op for op in self.sc_ops) * 0.5 ) + self.M.compress() self.num_collapses = len(self.sc_ops) self.scc = [ [self.sc_ops[i] @ self.sc_ops[j] for i in range(j+1)] @@ -50,6 +53,15 @@ def __init__(self, rhs, options): self.id = _data.identity[dtype](self.H.shape[0]) + self.pre_M = qt.spre(self.M) + self.post_M = qt.spost(self.M.dag()) + self.pre_sc = [qt.spre(c) for c in self.sc_ops] + self.post_sc = [qt.spost(c.dag()) for c in self.sc_ops] + self.pre_cc = [[qt.spre(cc) for cc in v] for v in self.scc] + self.post_cc = [[qt.spost(cc.dag()) for cc in v] for v in self.scc] + self.pp_cc = sum([qt.sprepost(op, op.dag()) for op in self.c_ops]) + self.pre_M * 0 + self.e_c = [qt.spre(c + c.dag()) for c in self.sc_ops] + def set_state(self, t, state0, generator): """ Set the state of the SODE solver. @@ -127,6 +139,35 @@ def _step(self, t, state, dt, dW): out = out / _data.norm.l2(out) return out + def _step_superops(self, t, state, dt, dW): + dy = [ + op.expect_data(t, state) * dt + dw + for op, dw in zip(self.e_c, dW) + ] + + temp = self.pre_M.matmul_data(t, state) + Mrho = _data.add(state, temp, dt) + + for i in range(self.num_collapses): + Mrho = _data.add(Mrho, self.pre_sc[i].matmul_data(t, state), dy[i]) + Mrho = _data.add(Mrho, self.pre_cc[i][i].matmul_data(t, state), (dy[i]**2-dt)/2) + for j in range(i): + Mrho = _data.add(Mrho, self.pre_cc[i][j].matmul_data(t, state), dy[i]*dy[j]) + + temp = self.post_M.matmul_data(t, Mrho) + MrhoM = _data.add(Mrho, temp, dt) + + dy = np.conj(dy) + for i in range(self.num_collapses): + MrhoM = _data.add(MrhoM, self.post_sc[i].matmul_data(t, Mrho), dy[i]) + MrhoM = _data.add(MrhoM, self.post_cc[i][i].matmul_data(t, Mrho), (dy[i]**2-dt)/2) + for j in range(i): + MrhoM = _data.add(MrhoM, self.post_cc[i][j].matmul_data(t, Mrho), dy[i]*dy[j]) + + out = _data.add(MrhoM, self.pp_cc.matmul_data(t, state), dt) + + return out / _data.trace_oper_ket(out) + @property def options(self): """ From 3bb5f71983a758bd4cd39b88883aa159a904870a Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Thu, 2 Mar 2023 12:00:06 +0530 Subject: [PATCH 104/602] del. non-user fun. in __all__ --- qutip/core/semidefinite.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/qutip/core/semidefinite.py b/qutip/core/semidefinite.py index 0c5774fd51..c6d3762f22 100644 --- a/qutip/core/semidefinite.py +++ b/qutip/core/semidefinite.py @@ -5,18 +5,6 @@ """ __all__ = [ - 'complex_var', - 'herm', - 'pos_noherm', - 'pos', - 'dens', - 'kron', - 'conj', - 'bmat', - 'dag', - 'memoize', - 'qudit_swap', - 'initialize_constraints_on_dnorm_problem', 'dnorm_problem', 'dnorm_sparse_problem', ] From 2a168d0382dae05fe8cdb2dabc55c91d4035e941 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 2 Mar 2023 09:16:18 -0500 Subject: [PATCH 105/602] Restore towncrier checks --- .github/workflows/tests.yml | 2 +- doc/changes/2105.misc | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 doc/changes/2105.misc diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ca207a7b95..844b3712d5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -205,7 +205,7 @@ jobs: python -m pip install towncrier - name: Verify Towncrier entry added - if: False # github.event_name == 'pull_request' + if: github.event_name == 'pull_request' env: BASE_BRANCH: ${{ github.base_ref }} run: | diff --git a/doc/changes/2105.misc b/doc/changes/2105.misc new file mode 100644 index 0000000000..9448f7cb05 --- /dev/null +++ b/doc/changes/2105.misc @@ -0,0 +1 @@ +Restore towncrier check From f714c387705c10c9e5ae63e05d77b2e3e173a32d Mon Sep 17 00:00:00 2001 From: valanm22 Date: Fri, 3 Mar 2023 09:41:37 +0530 Subject: [PATCH 106/602] Update links in template --- .github/pull_request_template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 45554412b8..d68410d7cb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -5,8 +5,8 @@ Thank you for contributing to QuTiP! Please make sure you have finished the foll - [ ] Contributions to qutip should follow the [pep8 style](https://www.python.org/dev/peps/pep-0008/). You can use [pycodestyle](http://pycodestyle.pycqa.org/en/latest/index.html) to check your code automatically - [ ] Please add tests to cover your changes if applicable. -- [ ] If the behavior of the code has changed or new feature has been added, please also update the documentation in the `doc` folder, and the [notebook](https://github.com/qutip/qutip-notebooks). Feel free to ask if you are not sure. -- [ ] Include the changelog in a file named: `doc/changes/.` 'type' can be one of the following: feature, bugfix, doc, removal, misc, or deprecation (see [here](http://qutip.org/docs/latest/development/contributing.html#Changelog%20Generation) for more information). +- [ ] If the behavior of the code has changed or new feature has been added, please also update the documentation in the `doc` folder, and the [notebook](https://github.com/qutip/qutip-tutorials). Feel free to ask if you are not sure. +- [ ] Include the changelog in a file named: `doc/changes/.` 'type' can be one of the following: feature, bugfix, doc, removal, misc, or deprecation (see [here](http://qutip.org/docs/latest/development/contributing.html#changelog-generation) for more information). Delete this checklist after you have completed all the tasks. If you have not finished them all, you can also open a [Draft Pull Request](https://github.blog/2019-02-14-introducing-draft-pull-requests/) to let the others know this on-going work and keep this checklist in the PR description. From dba8c2ed785a07c60141f4e04a320743aeb9e1d6 Mon Sep 17 00:00:00 2001 From: valanm22 Date: Fri, 3 Mar 2023 09:56:16 +0530 Subject: [PATCH 107/602] Include changelog --- doc/changes/2107.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2107.bugfix diff --git a/doc/changes/2107.bugfix b/doc/changes/2107.bugfix new file mode 100644 index 0000000000..85e2b12df6 --- /dev/null +++ b/doc/changes/2107.bugfix @@ -0,0 +1 @@ +Changed qutip-notebooks to qutip-tutorials and fixed the typo in the link redirecting to the changelog section in the PR template. \ No newline at end of file From 5a6e8eadced14477164b134331262290c91f3429 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Fri, 3 Mar 2023 10:50:37 +0530 Subject: [PATCH 108/602] add changelog --- doc/changes/2103.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2103.misc diff --git a/doc/changes/2103.misc b/doc/changes/2103.misc new file mode 100644 index 0000000000..aff1b7eb93 --- /dev/null +++ b/doc/changes/2103.misc @@ -0,0 +1 @@ +added __all__ to qutip/measurements.py and qutip/core/semidefinite.py \ No newline at end of file From 4d7b8037716e9cf08c01edd580253da63c2f0165 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Sat, 4 Mar 2023 03:01:51 +0530 Subject: [PATCH 109/602] version_table call without cython,fix #2001 --- qutip/ipynbtools.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/qutip/ipynbtools.py b/qutip/ipynbtools.py index e2d906b637..f1a5de7d01 100644 --- a/qutip/ipynbtools.py +++ b/qutip/ipynbtools.py @@ -5,22 +5,22 @@ from .settings import _blas_info, available_cpu_count import IPython -#IPython parallel routines moved to ipyparallel in V4 -#IPython parallel routines not in Anaconda by default +# IPython parallel routines moved to ipyparallel in V4 +# IPython parallel routines not in Anaconda by default if IPython.version_info[0] >= 4: try: from ipyparallel import Client __all__ = ['version_table', 'plot_animation', - 'parallel_map', 'HTMLProgressBar'] + 'parallel_map', 'HTMLProgressBar'] except: - __all__ = ['version_table', 'plot_animation', 'HTMLProgressBar'] + __all__ = ['version_table', 'plot_animation', 'HTMLProgressBar'] else: try: from IPython.parallel import Client __all__ = ['version_table', 'plot_animation', - 'parallel_map', 'HTMLProgressBar'] + 'parallel_map', 'HTMLProgressBar'] except: - __all__ = ['version_table', 'plot_animation', 'HTMLProgressBar'] + __all__ = ['version_table', 'plot_animation', 'HTMLProgressBar'] from IPython.display import HTML, Javascript, display @@ -39,10 +39,14 @@ import qutip import numpy import scipy -import Cython import matplotlib import IPython +try: + import Cython +except: + pass + def version_table(verbose=False): """ @@ -67,7 +71,6 @@ def version_table(verbose=False): ("Numpy", numpy.__version__), ("SciPy", scipy.__version__), ("matplotlib", matplotlib.__version__), - ("Cython", Cython.__version__), ("Number of CPUs", available_cpu_count()), ("BLAS Info", _blas_info()), ("IPython", IPython.__version__), @@ -75,6 +78,9 @@ def version_table(verbose=False): ("OS", "%s [%s]" % (os.name, sys.platform)) ] + if "Cython" in sys.modules: + packages.append(("Cython", Cython.__version__)) + for name, version in packages: html += "%s%s" % (name, version) From 98fdcbeaf7dfb4f3d68371d8c443ebaaba65b1f2 Mon Sep 17 00:00:00 2001 From: gadhvirushiraj Date: Sat, 4 Mar 2023 03:21:45 +0530 Subject: [PATCH 110/602] version_table call without cython,fix #2001 --- qutip/ipynbtools.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qutip/ipynbtools.py b/qutip/ipynbtools.py index f1a5de7d01..8bbbe052b2 100644 --- a/qutip/ipynbtools.py +++ b/qutip/ipynbtools.py @@ -5,22 +5,22 @@ from .settings import _blas_info, available_cpu_count import IPython -# IPython parallel routines moved to ipyparallel in V4 -# IPython parallel routines not in Anaconda by default +#IPython parallel routines moved to ipyparallel in V4 +#IPython parallel routines not in Anaconda by default if IPython.version_info[0] >= 4: try: from ipyparallel import Client __all__ = ['version_table', 'plot_animation', - 'parallel_map', 'HTMLProgressBar'] + 'parallel_map', 'HTMLProgressBar'] except: - __all__ = ['version_table', 'plot_animation', 'HTMLProgressBar'] + __all__ = ['version_table', 'plot_animation', 'HTMLProgressBar'] else: try: from IPython.parallel import Client __all__ = ['version_table', 'plot_animation', - 'parallel_map', 'HTMLProgressBar'] + 'parallel_map', 'HTMLProgressBar'] except: - __all__ = ['version_table', 'plot_animation', 'HTMLProgressBar'] + __all__ = ['version_table', 'plot_animation', 'HTMLProgressBar'] from IPython.display import HTML, Javascript, display @@ -77,7 +77,7 @@ def version_table(verbose=False): ("Python", sys.version), ("OS", "%s [%s]" % (os.name, sys.platform)) ] - + if "Cython" in sys.modules: packages.append(("Cython", Cython.__version__)) @@ -374,4 +374,4 @@ def update(n): video_encoded = b64encode(video).decode("ascii") video_tag = '