From 8ed738b072242f0123f543410747bf6e2a38da64 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Fri, 20 Nov 2020 21:02:00 +0000 Subject: [PATCH 001/246] Simplified by removing token. --- docs/images/qsimcirq_gcp/connection.png | Bin 0 -> 169758 bytes docs/images/qsimcirq_gcp/container.png | Bin 0 -> 158370 bytes docs/tutorials/qsimcirq_gcp.md | 69 +++++------------------- jupyter/Dockerfile | 5 +- 4 files changed, 14 insertions(+), 60 deletions(-) create mode 100644 docs/images/qsimcirq_gcp/connection.png create mode 100644 docs/images/qsimcirq_gcp/container.png diff --git a/docs/images/qsimcirq_gcp/connection.png b/docs/images/qsimcirq_gcp/connection.png new file mode 100644 index 0000000000000000000000000000000000000000..ca66bc8aae25c78c166f2acc35df62810b95dcb8 GIT binary patch literal 169758 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%0D&p{FWo3bWXsU*F$Wagh7Ungt2$w|}zY3gArqdB+ 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

RKu#BAmLk(k!lwDFG9&w z!n(IDSrJ;2KiRRC0g2682urRJlC3NJk~s9|-)L!Agw*~}I_M2bqPub$V}#&QPV;Bv zu=2Ft_iwM=i`hJDSjbK`bZJw#%DlRo3zD6>+DW_G2u`HJafU4FKrrDw9mzq>3K%dM`my|k|Iq7xXj}=rn zxy&GR0k}UQQLTuk@Jhrn==gAdXkM$u3hWT{ASVVe4~(TyEAl}hsVLmI>bcIeOVzNv z>drx0KiRf6{zSI{|8%xAtGpysTy@$lOS}4W-8eX`*TtSNknSI=iE#9-a5=gRrJqV%;cPpLuQPAf(MCn<@+Veigf!~OLEKTC~^3` zJV_UM2$kLp#4V7wQSmzT^tdq89Hpd^+h8NuqEV|gulcuU=Z~)Fs19YoPYg*?KmJHJ zDflYIJ78btVN4Z+#XWEu5z8^pCsHv`FGVb937dy5OiEuVVkz~u44;QWJ?DUas?-rwy z_lja4GyI87O=H)Rbkg`zSLjJ2)$Vs^ zr*PM@=o{M4t-Nimi6Dta1Ls7qI+?POv}yg$W7kE;f`yb=1{dvTE3wT(Bnez*thx}* zkGpLXV`6%;Ce9vW(%rO)Lrcm zen5f|>?zW3&WNe8sy~5#-9u1(1_gpr;*)ik&cg8;p7QL#ELBQS{nw7X%O!u>z_|kZ z7ad&Fde+%)TD5zf^?${1F%3b+^U>#bPfx)9V}sa|kte!nR~%$G^pLz?5nn@#nB}p> z3s6w$S8{Q%kdcwAK5WFgsA}nnMNl04_!ju58kL@<7MULY%l9T8Fxo2 z_kRItQSDXs)vyoiP1e?LkH6Xc4kDbN2R!_@NJ3d7X4XdMdzpQuV4K7vhD+5{ULNls zjTR1YrLwQ|<9D*Bxr>=6uymFg%84Z9?o5EMQ1!1!Cmv2x1o|(2+Fz0uy~7aHNZ%JVD6njW zPUq5wSFCmVvW!x;_b%(3Tn$(?g@JqqXF?+XLI*xC-3pvg`!n9X-x7S^&o8WC6t{aC zJ@$?@f#xV}S8)BXe6rI-PH1?V@}Oah2x4JbS-+%kTJAj+)3fQtLSONCOF&GhLx6J|

uxWNj?B1}H~UYCmoSwJ7hhHmd>XXl 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 literal 0 HcmV?d00001 diff --git a/docs/images/qsimcirq_gcp/container.png b/docs/images/qsimcirq_gcp/container.png new file mode 100644 index 0000000000000000000000000000000000000000..61ef9c077d69196de1579bf08a3a8cec0944f0c7 GIT binary patch literal 158370 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�w+pk)NLlSHHYg4nPcehFS;A2tLT;E&Hg39k&9Z4 zN%vgWOKN*uQlWL_WCO?x{up;-s{3{`Z;3!az*84B9Zv@)?DXRnQ>c{KR zzTUotmn`h?bWEvbR+=|Q14Y*eWtKr%r{-Acc&>}+1n3dCiamnfJhUzypS7Cv#q<{w z>LWMg#Kn+%5yss6SAZi$c+Owpinix4lZrffJKDVh7clC_A4wRuOvhg}j~A!}0f4wF z_-Y>S#v8K2l6WcyQFv<`!0J)vTzR9P7w6t2vid2H(+<_FGk`lg#Cz9)B$>J(Mo z3>_|)%&B8WxS1;ONRm#RK{?5;Woj3XTjJum|uyMDz#$I(YDy&2Q*vJ zWW{k?mU_ByHdo4B(ba#VQ=nJ}QkUQpc#yQXHgclbfiD8V8T*lF2b?pR$Z1Msdv}z| zUh?-joJv5&J#NF^9`Yl!OisJ|qN9)$J!{<13`-+)+2%d%O%UJt;b zVdFZqvyV1CXclew3-_u{AK`a-JpIswA0C7S=BfCqdBoOI9=?EK6HKXoJ;}|RP07rr zq3HzMv5PLJiw-ThW_zKGKR(MPIO<&17~M~2`OK^@#Ycc2vwWsGZ8KB7D~9=(s&H!D zMO>Bhg5*U9D7{635ot~);_KXtuedtS6N}Pp(56ys){OE`zsjj*#237B#wWsRqHoI{ zf95LTZa%T{W8a9cD@TysMyz4*v@9!SOoJDhH#3=D+KQLc7^AOr{iM+&$ zCovG3hV2OY4bC(!x2VxO zF2c-drUG7pzHK%4`th)^9jyiX2#<*e_|CP?=5?GcN4GwhN$dD=s{?9 zPilZ$Ni-2()J&Gec#5QnezdeHIH1P(MEGfE9!pdUePqX#B5!53#!k9KBhT4d_EviN zTEnc?w&;8rd}S;C)o+xYUoJ_X>bG)Lb>U1n8Tw{8U3KLY6a#Y@N~Fr}8ufuRHL9UC zM_QAHXWQ*%`)LOwb72R6tVh~OE?FJzO~lxxzB1feKM9O6uY>A8qQHC)V0+KUeTnLb?Q%m~?$2L}{@S%f_x8yzLN|x{ME%R{+HAo` zeLU(SQ03xkpVS~)%W@5LaKmS9U1WrzvD1mdzq(>iYQZ5dw&CnDB-2|ODGk%S-he(e zL|`=~CJ0UE!SAPSA_Q^@J40r8M=v9$20h7`kk^WfFaHwa;6->~?F0GN|2+gG9JqPr zquJNtl3?P6_z|?5IV5hROb_Pyfa#a#_@|OJo3lZj3-0#wD|aH)xVk|2fm-Z}9+z{W z_05L$%MuKJQteJlrr}T{Z4JbR9Q*Pya^d_mq^is2Q?&FC?mm*xeGe?C1W3Yn7oz>f01&d^Py*^C=u zr$x2(D2>AB#HgSYMafL3p6&9B;P%d_lF?$iTJAGrRVpt;Fy|3%TPCS4#{{$`c~3b( z^zHn0M?;;K-i9{09fgDxK5vz=uxR`JB?aqjlRFD18yg-ez5)sny8-D%Q2{Ypw%2-u z9y>OvvGXbR+pFVMZwbuVZpg}=h!=5Fauz0j1EwI&tdkLR(8Jd*MMe1RT_EiF$2JJ& zwok{OF2cZJ@s66qlwf}R`QsLcN0jCnK2LM6jsuPtS<8lgFnI~&L8#lu_v=pCTxNtE zlQ7lg8J~#rKx1^9*E=c;<%4zAR^!Ce*R5j?d7}-_-6;G|gBNysfA_3OH6rhUJjvyZ zjoW}*pDqKdO+B2-$b(D8q7jw)agtcd8s{-U|NiB0Lo*r-ym%hCumj8HaLs+28jqFq zlODoGi;Mf9*j&FBuSmV}GX`UGAO4(Z6Ug07sGSQ^i&-8lZebvaww4iNCRLn(?qS+M zcssOvhNIamYrb{jvZz3d=9hvJX18T;@rn_+Tg-C3T&}hj7jsJD=C`NvO@!^+XOme2lv{^MB9_ z{$tAH%=`nyYj{UB#Ym4TY|K=Sbs9A&7!PY8JAYf)cS5(I60n_o+gP%v z+;AfH!i|h^o^0G1Z#Plw?OSdWbR1&o))x#PP~N5_nOhpwDY+7zb)ZR>yOAG{&)%eA zvi&Zufe{oKmL}z8V&Xdz%$|2#Bon?vmrFmp7hR2*Qi(QR?@<9Z`&}+Tjagyv_YMnY z7gpm=;v&^LOA$f@l(+;ZP4P4RZTt<_m+r$4FOjR?I`#&ecR=4|F%@G`hUT;{I+Y~T zA&v$n!srlrRZ3!l9obJ z`SnwaJDks*Bpe85+92B*RL@Xe~X z6%iUS?j4qakRyAAUSe3_R}$zG;Ow>0YTY4SyW zmYdU^Zys{Q%xb3Fm>dPsA0_*zB0dIo^>Sd27}Guo8&QooQmR6Vc}&)S58P1zAZXC+ zr>Wjs^fdSiln(PXWG{+}xSUjtn2@wP@y9!~yT|)uTUtk6D%T;d9F7JF z?pxZ$_PY%L1}oe87uqcC9X8dp`VKhPxq7cVbsr5ir?VYqDe`A2;X%YX7t9LE-Fj0jDxyKMvMjr@ok?q%OTUJepSro8nO z;mk!y-C4(4W)FMI^VG&0ic7YA0~S^6Pi06FP{3bSFg;+RzVfd!y-lI$Xk7bq))6Ds z4T0OPU^S^CH~oY4zGcqJEbAqkK@GNBPYLu1=m9>{Q6Q^~wmhT9eHB8#AQ8~`;BT^< zv4=8=9nRqB*F=5M2+KsBdg9e#esS|dJ}%9xUsM)t-Rjafw@cJG{`*%3)r`|vddZg{ z-rVQbZ-T7XWkd{#c$9DOz}6bss!uUICB~B>oAQw=ZG`s%D{frZ2P(XVju$%uxIyF+ zh*gEabFk%kUPNckg?8!i4!TJ3QuUQ{8-;&aZ|u?iU1E2Cx#1aJNcAX?LWwi)@vwfS zLVmRomW6Y7nc)a-e`Md#%L$C?BD2Z_03lCM;^)NYN4b4d`$k!67DgRJFz1&K=4EfB z?}zq5ln0l)m`MHl)~mz7=%8!CccAPg1vY2&#( zSKPK@;$yO$bSSxOV>EwzHr*S?Zkzh!EX@@JIAAE>A_7vk}tY{+zK<92;n@?Tjz>}(X&9|PWpbd z7TUVo1N*4M7e9w((zeJ~FGbJ3`s&Ag_T!At0&Qu);Jx-2SBP#$ogI$gIZDQlqeyj* zQkuX?40$*O^E~xN8iU-j*MfS8VtDT*fHwZH>m_Yi@Wn;Dg(+en+pcT?At2Hi-~*%XPhB?J#u&?!aYF;u9od^?;H#jm7;=Cb>ZZzajH$&aulEHK;vME2 z*mnk@9C&_krL~lngEwP5j4=TGg9%bWhjs4Nm|u?ot@e7MljOm#fPxr`CkF8O53AW? z>kwkLW6V~!8;Xt%WgyP%?e0j2Z?VGdFr4as>eKp_Y2Hto^|IEL(3B`QuimS|nS*7m zj>8ZRbma^>6=msw2W+>rjB<=W0n&G~#br^BjS3uqcGd5M;N4z@OjaXN^b0KcGiizA zm{@Rj^8wH5RlorTyU)4isy9r&Jq@4|(?|Gb0d$SJbyfR~k;BN}m2GDuG7HZyA3Ces zjG)R9C`(~{HNQ*vl*a##>sosSxnvE$565LA7zYgKH$L8#@~Az)uQCGdsgt~@g&Y7o zc*pXhlvH#0O$T@>fQ;oC;szEsT&G#--BH_;pUdSmZK#jm|8O5cTiGrlYIk%en9*5J zTDD{&dWVk{7DZ?47_K3Hb|6Xt>+&M)eqhvlRfMYZerQ z+C9>EVf`Mo5lD7B2yLK*w>=(V(yq*@Ohd|;3>rAEQw-=-xC3XeAg#*>`gK>S_Z2sd z?-@UfHy>`+w$9b|CaX-XwA4MC)H~jGAuZ@+)T_c)B3-2^9$*^#p86HH$Y$}?g3)!W zX=QPtVw`l(i|KdOgcZU$L%(w}MrJI0*gBxv?h@)w?kL5NQ1irc`EOqyQx=K=^N z9I5Ge(Sd}jWEC0g_^EcsylH8C1NQ?PV_{Y$Ume9q4@ksU>@IF11%0~$IsJxrMybV7 z>9QaRxF;nv_XKX^W(;V0Q<;sIfum4}&c~BE0wVfP&_ng z6v9Dv`1{)tYI|H8p}1RkUvvg@+Mu)R%DbBk$5u=O-uK*R9Ie-=JDRr1r~%iS^X+TN zGC8o(Y#i1aSDp%==?@*S(3mGiPHEA#L= z)$S6SHV95=>~)EB@` zfKQZHSHGYAkZJ$kCCTBxVc7X|9Y#(x5bU6#asX9pX5JZ58HCc^RTezmIVHqEjopUJ zH^OVk?Ur@~-}lrU2X6a0Blm#cho}m)u38 z-q$87+K45}GV4l6mSh{#g|0`8X+h+&x9Z3?X`E9vG2gi8Xu&rHFd6r>LDd5Fhe8^? z(V)Jg>l^sYgxOc!9wXE_W)m@2-S;9J#KIqQFq7$;XUFU$#}~)@T7W-w#g^HV&0`jw zDyDd4WhiBmqi`9;pPsAjxrjlBcUV{V0wHw^r4 zn=Avc|Ix^vi#y~QV}p=){rd40il?G0sr&5Gy+HII_5iI57RD9K!#&_9XS;$k1rFq8>u-?bVen=_#BC+KW(Afk|QNYR9o>IJk# z6nA!`5f)eiyutP=ZJRV;s-SR_;3*$>>(_A5;Ye#c)7;A1szsE8B3u(+J@z*{0tTl* zC=GtK<~t%lC-EJ9=v%{cs->P!lTbUT`~hjqr#K_UFQMM&-n@gVV{6QAJLc9x0P4x%;Rw|w<1C% z3TdxXz6oMp8^CS@2|VIF7gy%*p6|SSuZ*EprbFY^8(lT-zmC=KSKlC;EjsJ^K`5zR z5b<_Z+JD_72o&--T+a}EKf^Bc%EPNld~-R#_>Srx_9*e#wxzJT&9Q>9d{tpyk=Og7 z*-SG)QVg2EBZBCRi-OU#Kg9T?7}1lRg6_kDK^vOhpH>H~al>rF+k&H0P&ezX*8f$K##Q6h>H<{3Z#Z25|3blxp$$VR{xtn8(tKxd#ffD= z7B?=+FFM^qvf$_%@B02~1@-aR(R(6CagID^x)toSNig>PT?m-O=+(U;e4cLXeIfwh z?eH$<@$fN};5G23>NE24otl*N^5fxVA_GW8td{gzzwQq8?%p#ZvEt9yJ$gyMg7zu+ z3rDU2?pL|Q9M3$wmHX`iJZ~{G9fAYH94SJ8+s@R%Sg(>6T zQkj|DHKOIS&FWHxs?naZ61K=o-_83}>rcKi9AwS{&oth3h8Ou{%iil%H<=FALwWr7 zGmN{idcw!>%>oo{FieS5mq~L_A1F>GmJb=i5s4UE&z3_vIFx=gI=&nki;lmLP)Qu1x12 z(Z;zC+|=uvN#D1ed;TbY``i*;{0FHn4lyRdIFo!Iw&I^QAm8neTo=0rkuMtoPQ5%U zju+l-5%~%-&iA%BYn6NMdnhDj_fNb`vHGovapm&e&t~IS9}$BD9qIxKZ(7d8s3f4q zEXS-6*W$i_IuP4+{nTe1KmRiGodtmu#cTVY>`&VFCzS#n%XIKcZvsV4PyXn6Tlv9j zoL6H5PK8#B1z;#QF%QvOdcoNuwp=Gm7KC7hq-m?xxSl5Yx*F{q|uq%RMiEamD_tX{U8FY-SeYmXh$Bs<$qp0SS= z={@e*ZZ6%wK}YbuZ*dc6Aa^`;%8Zh`pe(pO<@NjG_d9AQ1`U5Plfn z7|}%rQSdDgr%5YXKM-&)^G@Qpqult4>-t+UFFL!(E$;{COdNk1V87c&9k=yf$!oZMRy=Aax{f+~O-=8P#UQZ{o(_5p(qH77n)CFK8-Z!T`LDCj>mLQv7p>F& z!h-6u+2Mm*D?{zZ!Ek`x(JA zJZ3KwMm-K)&dzZrmV+%A7YfAjld0?=6xy{c%aktGf@&|Fg6$o3qJ3N?#H7cOk3N+T z$di_k^Jtax5@|Q3S?t8KY-wg@A((+I)-hg+u4U2GP!XaqlgP?$QafdG%kF#8AoB{o)#QpQCHc8Pl7RF%KdK4i3 z4s`U3NW!wtk;NTrXJjcIWu~PPL+7Uj&FU?ULcU*rcV!M^)E>IutzrLS!0E8oSpDK3 zVnN40ekKDW(H%bU?(7Jh*YtiK5iFh1>y`PAs-IB6<-|%OhyD@q7>R)|Ydv1nDhT$NDgQm^0K^O#xXS&hRK>Ybx!7Qj#U zme27Q(^%r~@b(oi-{(7+!lL+#clOLqLthC4IS43lGdZTzb-bw z0z;76qV^%2gL#8G8hMe0-+WY4Ey@=qQ(M*OA8V`Iwm;zj%5t}_)nw&BTk2j^q_w%O zl*eE1|IX+M$f%3F+lPKNln(#_Zaz;l5VkqBnx~G692=b6#DJV)%`<5xRuWYdh;Bop zsn8lv6Z6r0MH!7D#*JruW10gg=tkai?>i)=FaX-6Mp%oNS1TQRwHW|ttMUO2uiTw> zb)=|weCbN=Z2rm{CU+^_K1cbKQ_$nRr-ZQ^H7%$2m+szk!j&gxJ+>1o9(a4;n4>#1 zLAxo=)iJd^Fpid-=x|0gid&EVAS$$V&&6crVH8cf?UeZBs(lh{ug{0-LK*zLzk)I3 z-RkZA?V)xWqxd5$8REiiyw}mgsqs^Ow$C-bI1eI8K|c9ZYy&r9gHS~{UXvB~_ZVo< zv{Xa!TL8n|#oRZS;*}j!DIv{ngv=U1xUabN))ul9Q^rxUbF82W$JbpAiAeYVR^s-*|0s}f;pX<_Z z8sXgZ4trl|d+yWC8jCFPb-HAV*6POvX8dJTwlSX{Mb*xZ>@0NI9%#7E;@6(Hyqj+b z-nIqo_w;8(`j`k?_A`yh*Up`X9l$gG{ZLa6IX z?SN4hSTg5x;4;AQF7K~(#$9vqcNVDX&W@j%bw#_stv7#BJ%@YK=hODn7j1`yr2EzK z9~anj0+Bvceyae&8RU_xDr448vn1dtVFL<64?|OS7U#urQdLnLH)LkMUb5_Osq3{C63ypj)nJm+{n`c1%q01P&h0gSSMUR7*U zO@l3=1=i;I4sQrNJi5LN@oynO{U4+!2$+8_)Zgo z{nG7?mSj7Q0dcr?8^j57D>Z5djfeRZY7ff`>tPlwmcd6q+MA6|x{-Ew3KkU^xd-=^ zC?MKrf=%@Rl2&a)Pr@BnXd-k`CF?DSrPO$z;LArf&<#;NV4=nu!|p2ZB5$%Q77S8q ze_P=fa&c91WrW$T{xh4j~kaNtvyX-KUva?DhIo{!EIMJ8*OUm1&; z-#v9VgQlPPS*L%16d|I{G7B#X+bI}@X4})iuluV-Gx}~iKQG@TQ@*D!4BblhPp0-5 zHS2Z##A@b@>q_&BDe|CeNHEHUA=1JMqK>90;v>%<7+?I*r#Q zT(S3F$+uJvAgL}BwWkQ?)XC_PRvAiuHqVhadZ{xwDK#8KIh-?6l2bcY3A7~>(=+*{ zi>|o1K;*X>ovNl76XLv8XTj_^tKK;SobG+7<|7=IAyD`$8)yhP%a=y?sWQ3#QJrNs zk}Kx%fJpXEOTXhZIqn^(D~W`a%amwJRD&AllL3 z@z;b)DzviW3OK7?(DM=;lgZoy`n2E>Jl_skt&3UQ3r1v|p zXCYcAU0i_};?&85KCbj^IizJXb$6x0<10u`J^el=^viQBxpt{}4g_P){f5X0IBut) z{vvN1oMV)Fezh2kV0Y_y=yBT+IF;jNPTMh2omZKG)=nDN&`V=h!qmtF{t)a)yie>l zz6fBVb$KQHnwI{Pod1k?hi8w}3vu2_+Y}I8WXO;zW|TkW+tHxb1hw;TfTGb;6S0Rj zUx`~14c~4bj!~(A3Lxx#+~NMrBU)2XzW#&CALH-gXP#S$=At(C2_u+wq*JHiRj;T> zeeb>vTgp!`2XhZBw96D2AVjX#f!{clOvy>cm*zqmVLFcyS?MvTugc z`}vd!V(Is5Dnn;V!uQ2aWiNPogS|T{2lDRE5FMiM`sW#RO5T%wS=GR3yH z)Z8ELlsi!usqJcyw$8L|5ekqU^O zvoPcH--^FuRm6OKX15o3NA)QSp1~2MpFF=F_M}h0V89xW__ zAYBUy!jKNmDveQ;6N@<~$$^>Qy}@hz2yV!bdu*C^<$l`2PKkI_-luyrrVVk0F}jT{ z{|gHL{NL>~vxlz>M+t%7(sAZcV9yWNMI-G4&-?5zb{T!t;~!$(GZ=qtT?(qw9=#iS zig;^2%$gfZo<5A1ITv^)e^_=Ym;`5p$`d)?Q4)_9Tgh@{hKy zHysv*_VYFQxNj44ApyVFWq~a?5}gl?+|~laf_yz9?8OumRhScTz762>q(LubB$oYD zV)43RHm+|j&5ZolLwXWXNt&!59$F;bJb>flcXExWVISwMT8XbbQh&_SiY)M7v)e>h zrlk2mB{bt+kxt9X+mJx|a$e?zOa^*51$wW^0C{qFN2ln6gDg908Tk9Y&5k9{<%15u zr-`8oc118EeftiLDe}GWaNxi%&!d1_xb{=Kg*PgF2eWUFj|yyMe(X5+=H7kTh{Yrp zxiL2}sPBY7THQ`o8|-!Oo5k8?G4EQi|8|EnKeQ4ZVW^3C0PKnwjtG{ z%;&ceqH`2~tG8btp%`R*Q`b_KJv^bTkVT(`c@}~x7~ti-Ynw#1+7?t!D}r=m^oJ)? z8P6F>D23=;Asw!-gBe-t$E|)~As_qhzn{yX^%&6ZvEU)SADhd)lD5Q+D#~ zRnhnjNq&_nR|-pekI!SR_bDJV+)f`l0rJee(Yz=mC;gCM;1-u=zgLZSMdhSr(iL>~ zM?!+WF)V|S4=pI>Ct5$_gqT+KYo$t<>Pzn*G1bCMxrYqnz&r`Qs@$_( zVzW1cY^M!3W{55f9kp@GlAuDM{3mfbiim+k#wdYHhyyn-LDapA$#PmS-j9DV8@oJ4-Z|?d1z4Q()Ye;rib5y6B39+s{^O?@{>L$d-Do%M1~!(tQrV9rja{ z;;R(%ot+b%p@|{bB1?|I%YA~`XPu17Kq8TDOwyakHsH5*Ghh0#IMz?U zm1M?4p5MkJ>gc;}@f*^0Z3An8(SS@?GAb1(P`8(n_Qr%}x|+CmM#oHHnuY7iRBkzPdPjkYa|*fPRHS zE=nTy!*3AZ@gb=dpPP%d6+Su(t~09MXMn)Uz4iC{r%@DZl=6K13Hr@>K7Wabbe}+kEQd zFRZAd&ygC_^*5S1q|;^uflL;WGFi2xf*k;l38|yqyzfoJg7U=Ufai+lVy1|?vR9^H zVKPu`CrR!z50RSQr>h;wpNa~M7#3w9<o&(m*0j9&hl;`uzW)>D$AZ{Qv(k=kp;+jzy-3qCyTMJkUyo584_=ZCiP*Vt0)&@3pO7{xdj)rQVDZ2Tf7bdP z$Gqex+4c+P=793H$x|yEj8Eur@VIhpkz5OHQX0knSaKB-0Oq~uQbgzAR zMBv7rYxJ*~5r*v~4BeFobN_%28Hq<0G zD*yGFoQbe^Jm*di_-$AIID#Gj(cquS1#hV#u<%lv(4g$&>pkyrziQ`y2u{LB2GU+z z?G_!cf!#>pg=rm8aSc;QVzzc8u z8oKw?`qj4|aQ7rgYRV6CC(-BSxmR)N9r12cMF_()&w}es7lO|s#7f#4=9})WSM=aP z0eST^Gj11bG#u6S5Pw&LM;jGA^9V;VA#ar9@9|-~Oms}RI@h&<+vOF!MKW4x7@(Ww zp~16PBT_YzUy~o(JRM<=e><^+@Ngt3RV9QrKyGva9cSo{e8kyN8e3oFNgouF3zIdz z|CzlN_bk4}hqHojlH&r!D1NNc3~MgYVJMBFe3rn3XkQ;sv_P~dJMHp4+}ir8W^<-b z=48#w5S}Le6g-}L?DlQK!J>w<(@sreWk9q<3vcWp zh-~CDUR6qS&H5!L_*w5V|9*q7rmHt_cu51D?xam#+@}_Pmv)~>z8!<~Zr_ukuy0X6 z`MA*2_lMrocg{S$Kik+-Km7*Z;?Eb;lAr~M=`|?HH=r5#rCh-@S7&#^*J!SyZrVLh zrQP$Aoq&z7ywmk}n^LCINvnyzzW^;1G`vM&f_{~*=ijz+0UL(!g?Z)HhC*i#yjkzB zxa+x==Ovkox&98SADC@5*DFwR8B(qh!ERrC@fpAoex0{sRlBm_^A?ejSF>~`e9at& z9mmj~pYBim`He3iafhaoG&(4%&!azF?B?)rm45g&nz_EH_@}+C!bQ{1smY{QI+9;0 z#tIBthZn2z25ol+o$HZ2|FrhLuUmT_CTK8mcBaP{?hP0a{}tq46vRF&l2m(_@_``d ztfWQ54Etlp$CqZ(O6he&?}Z>)Uc4j9`ro*QFy(u6H%oyy&Y(oex}dZ=63>Ww@4Bf* zRa5M3%J)x*`X}MuV|OE^dcl=6=i?Rnr=jS`#&ac};hCvAfEZ&=h(r;UVrK|KmL00(Fsu(ZC(Ogxu%HKL?a0H+tixL>?F0FOpJEMY(oP733j_ z=T+H>%?<}mZ_5)qT#D90H^Y798|1z*H0p+jeT~6A-%yz}ALT5#Hl-b>=S*hv%~fj1 zJ2|><1iC5T4&-!~y>5Z1vlo*bNqTVs$`rFYRGv_Gse^mDDNNOjuy?_AsiXOMqw_@%=FGmfpU1YoGrM-q5jlqPi|PNN?&=_yzn68WE`|*G_+&tJ z^T_6r#OW=Sr-M=j0S~SfKi4ZduS$!Q5X76ac+k^>Hdimm%f?*m;gT5Z-J(}qZBJq}zh`XH(`SXhd)~I-pj`)h*o68@ zTGUI3c$h)?!#C*i<-M%r=}Ap zhwEIK-|6^A-R3^;UZt!OJ(KA2EtBP2#EV7s)hZZv~3ikLDHbF>G^^nW~LJQ3U#Wyvq-{>N`mi2NYD z{f$x9Ugq+n=vZ}jLgBR)=HwUczl?tL0+sR%1l%)fHP78JyF5l`A^DBdJs5fUUk#5s z5*T`!*k#5q(Kq%{M(10@?ZGEr4w*8YBO2XhF_-%et1I*$+@Ujem$y3Q-w3)rwdClsU6fAMVF{dqpA0C zhnIMlT5I@f4?j<8LeWu52X^%C-*0sVtq~6BL219NLQU>T7MT5Yj~tT@{u)e#gvtD^L(K&i5E^3V!M_CaN zZpa4wYA(*jJ4oB(ef)I8KA8(I`|ZUD-!L+Y+t6@iw(IY_yVOT&r02qOrb&JKajLf{ zS{?;vvV|4Vf(-`-1 zEt3ygBl(QB$dTty+yicHketF^zqdNq{#z21RusW*M?wzP6!nkTJFARX!ZazZfm108#pTxLZO|Gx{M zv-@ki`|3sQMd(N~0C2a!ZLRtEVVnFVqo2)za}9(|MMSiAWcLY!TlF(~dEZa%abK#2 zWJ-+nR4#lP#Oiy1qUR&cjAfw5?+go)HUS*IPIJ=i=3j6l@kwvQm2Gr_eIr(HxGh$e zhYYO?F;L$&I={3U1~Z4C2ak|!7Sf%MA03@viUE3wjR8I$md|>Pvz`u}}`7WXR!fMNRu% z6Vr6ywXg1odl1}f9JT!4!Tq7NEx@no@(lqDr`4xFB;{44_VbDjYgGlk#MJ{R?>wc_ zdG>r2k=1XZbrQyO8Om^Ww9iFS58gGe!Q~f9~XReM63wU z1n+I@USRY~>v<_2`45)&Yuu8&?>!Cg|2|oF@k?<4wZn2M^5Sied(H>*G#|N{s<^d* zMN7()Os~Ro8%h=C=D_5IJRHqYZ+T*nJDrnkw^Ri*M!UVGV9%}X8swAM7@gsIw~Fri zZ@)rUnx85-{`!poWDI>iRd-#~8+DT5W zOU@WzY#*qJEx9TcLDJ(;UL3onMx|2r${u5AhI1m17Drh7DnMIn?DN;n{FnE)x%Lz( zv-P^JucyWBC2I#9x( zz@42K*SrhwA*qQN4eupQq^hgk*)!zU>i_VT773UZ9RHtGC}1FT(X)#dC#Pw5-L#Wo zKu~(5vprAxV6D@4Ts}}FJS57NwPcU0wth>WHkYH01!(#rH*I!~m7;m#Mo!nhsq;8@ z{+9{na$ASJ9F|xZx$YEn<>k)m=fpp#_F!dIAqQ*zm;27od}9qz@sukxjM4wF1JQOjR3E8X2|H8Ar{$kX*e~nuW z6MvYCt1D$C)ErFJBBF;|PZj|=@W)C-eTi_p`0zr%K2FAaph(2>&e{97;m zC9%(5fb-_$gHjj0FPYqNEboXlQp$$EmhSwuRnlK^?vhiY$~@fSfUg-VaUbOxLl!b& zqOeaVisv3dmHZ1a-Ukal0ifQ%o4TnfB)1R%B+wHDolNm14o zQa^erxfL#O549eD4e;KHqkGwD&$onLW80Y6ax+Gq} zvHOpWMly^%>75n} zr=F&7nkjjbZ1zUY=9&h=P(O@^J<9R}Ud0y+L02wyf5v4PO^l3{53*>*S~Njy4L+S= z#E^HcZ+j#xAbZ7al4gIV9wBB*N{|?=fy&a)D>Zy-Vka&bI2W@$`vHwWg9&lGQJ%9sU8yhs8@^yMpS3vTzS;ytiIPjT7 zpjg#{lIxFm68Y=gG!hvJkcsbLQagOQ!l3c?X3tO_EiQ)|M$>lj%QO{aJ(ac{Ea*Wo zQA-foODD4%8w|E)H2ph&dwMJeo8L zj`BZBrDRr()JEzD^tW}D7^C99@)14X z#vYCUhR%K&x*wR@OG*a9t*+Z89#D$b7g~!F)!6XgO74M!hb#+vhLZ<%zo*@|@XlzM z{<&Ht3w3Gi71G!^N3*_V48C|vW=1wDUa@%E(Y+vxsKZwy{~dyv@&ZepMSHkI>G6}( zIdpa>!)HkDkvIxd9Ppi%sq%F|s6UhD?w^c^s}I%|ehrUsGTOHtLt)=f`{PlvGdK>y z&sUSoaLRBQ@*Z)YplsGi7rtS$2?*u3W!2b=UqPy*n*baFPOl)pPn3|>(n?Bx^R-B! zHvf6j$`@SaZzNxC`5@jQt!v(tPKY#Pj=J1-Ux6Ux-I=`;vDcc&Fl;Mtc1IK#`%$|d z?%Z^0(HGR@{*TkwIDMG$yp|E;*$I>nkNov+kWph44j`O>!a&I)@y&eBkki7^bdbN=3Mg{cnEW>%4Vi*2$_8Aws z*o7XSgXUU4c`SLQww>O0PD+V;qSS!=uHvy)vsd7!qzl(653gCD^PA37qWF6ZsMi^9 z8iYFObLpO%|Cd3ry_#84S@St4=dJ_kaCzr46srE68Y9L(eMO;}?g(XnhhNpNIDXX& zMvd72TcL@~$4JA)-vC#?yYAU838@>exGfpP=0Bb`n6I@Z%k<}m>JdR+f3A)4bzCZE z`x38G(z#S?bKUH&-~K!ko>Wo`NK)I7b-YF7!B!4|8Frd>S?wAgt!%Cp1xAWv-^HUw zQ&zQO(*Dp=v-}-AO&$yom}ctDs%M27G+#zLQ*+S!hU`||{`~zhLDF^GHMXK(x_|N? zBx>=Iw8Hb~r>a7vuxA)JV5(JrWv~6P3n2lO3`x3n?KnQj{mz;~2mbX^(VjSyga7+p z756D#KLcgIqn$e}3k6Rtm&!ag=wp&H=}1?kcV7HamxJZ0!+~`!MOnyeLvwV-c7E6E z%&SY0*|lxls$Tm+OE-bNcW=MMbB?NfN^MsBq(G5SzQ2Oagsj9knG$%`Y7lTvY|@*R zGPNHL3`4+(uQC&&^w4PuezT!$75a{qpOeUmk#x$5i2U)V7MtCNW8iA^3CSW^MD1CB z+Q(j1zb!RZ((G+HIw;jssxBnq71ClvND~#-#N@Z-QOlSIO>{Pr2;J2gC0Txq5l`^} zZE|jbcUtRf7K2ZqmsD0Qaa12*zebjt3pb%7Z;NaGxQ$S#v#M9{U%s^Wh;XKepj-HA z=(My64&!dG$mv>L8cWlWfWB6`afQ7%h(Su81%`ly{Vp2eNWCgi0$FbB-xE(&eLc5K zpE50QZ0~}hv&S9JMWo3jF;QA{r*?W;JLp>9R5Byakx*yzI@b~*r?157jMkqa6Tley zyr(9ln@;V6ap0ymEYyE|#wB(mDqh>C8{aAw0MNB3IFupqJi|h5Q-j3K&Gq9YC@wbM zqw-iOA2d(1XInYgv#V}DP^+~Ig3mlx`c7($D-ClS`NDR3^`nIwg5j>=bP@gyC{n@J zbR}eP*QQ|PZkcM`ykH3onB&J?caNNooT~PqOIm3=Zlf^T?EW5U@r*5lv8q+*Vl7QKJQ_pF@0)j^b_{ABIcf=0cr^HCi_D!MwGt)JnlOVcjMnW@&NUxj z9<5=yMISs~PsDg+jd>lQ4X{0Csp@}J>}3MGL|#R&e><2oryv1bu+GrF=-uoQ5QF9m zyy{Z48JvE~9e=|+)3qRJ_D~b;>#{yfaT+;dv9Ep3vD765q}w!s3OB!m&HUJR>XQsa zt8tOu$CWZznPf8NXu5EE4-#ULUQNAXXVKN_u)G`QN}3txGeosh`<@uc$YiB6w=gG& z(@oWwGg((h6MQNlf7;hqJAX-lbatCh>n(I>=f;JgU^UzbnTJ&~G-u|gV|oJ!k~(uX zWHu*>s+(LzvI$5#d5^UKBTP3iy=BJH9adb<_2Z489mIb#_|a$9X=UWel)Z~bTN*!_z` z5j49skNjKvn~npaBJ^?%AKZY=mM2o^{{%I|m|5|d!p^O8-5#@B!72wj)XEQ5uIz$iDvnCn*4_99NE zD>&ps7akz*@EC_Z{31&`68E^ULJFTCVT~L1qa%1UYYrmp%8g=-<3X;0)^Cu@ys<3! z=QGk>*LQ>oQvba*iBE^IvD(^CBW^UqkZa$iyl**Wb*-;$)kaW&*BPt+6JVr>%nUzW7Ar2p=%z~g~}p@CZB=d*GMVD;29sfbjI z=mK{zb1`?kShq#$xAnF^YY9_BPaew9yO{}rlJX=QY$T4@k_=a`B${563>lbzcJb)$ zQA)7?h~^67$g7$OG4gidK1O4U2a0NceHx0C7&jw@F8Arf;6?3dTS8+zYQqc4hK07Qczo1lSeNZ;ZR6Iwa+S6S~n91e75l_})}D@wSUt<>I52#%Bx6vY5l) z21`8>5V|mz**NXD3w(k~ybo#5YgTN{v&!@6SdOQ3X4eC*VfRXmVYZt#^16LcKnk+twevzT)8t=xFP;K`Pi6BlzuM+U&~2 z3PeGeqgSR7La>?FuLn3s7piqE>%Cw*0Gj}(94S~YsgAnbf`CP}&f(YH3>htJ?8Ncq zTS^LLxo>ZxT_1KK1iey@3jGKAmv}-=iHPN zAJ0erQJO&C5#|8>CHTVYu*bPQh-|*GV)1CP%3Z**FZ+u4EGSF@ecTGS^F9_XP!ITY z!!Dja+VP}Efoz&~zMbj3l7W5EI8njKpw~!`02=LPU z&j@}94J{0@wDy*^1j?c|jV=8z$;a}4io`0Ctj4p}`yY8B)#TFjm@Q}9dOTmc;2Z^-3&_c7}X~HaQGaMq;9m!u}2Z{6&#qS6V>!l#cm>w=_Ixk zjuoEW5b*o4eH2lh{g-Lc{Na9b+17N4t;P%&sorIPL1eOp>x-yhI6XPnhkSahy$~vG z@z($fB=5J?Mw31=`Ra%FR+(v=ML&WyquY> zE;k@X{ouNt)%aPR+_d=Y{L#hfYC0444|xW&iUs<3KI$9710_a4bO2Mn&|Sj>O6))+dwuLq@5%a+|%2*z7F;QZXE!Ys z?1*^U;<=wfc%}utv!*wgHaHPw__)?*^6pZT`#4>YzTio|}?p z*2uI!d-*sYl1h-1P!LGMDc2>rE@hr(l_4^;_FZCoTHgMVz(dddR8n%x8iOux z4KKZ(B?#AHw6}WL%5vF4&J=J~w5X!CO4U<=nQt}%OJbHTkljV8EZccSiUN2);FDEz zp>*A1hku0fQTwCf4^^8?ffRbk=Z{K|POvy2JjYNC|$yxCt6vK3t zV#xXrwLAVYYy$Fws;)e$;%0;p^+>0kf!V*zuKLMi`^0r4ID`}!&Ksr&omr!Fy#6_m zI)eH{KS|i~CgZr$ZgBr@6Nd655+tDXq>t+^;j*HqVtAvWx|4i)9O!vgj@PTtwJ`^j z5#x3{`)yDG_0)EiG8L*blJk?-nv`=H`N&)7MgfX-<_M}}TGD@u0s2c+_~ggPdG(B) z)?3b{gy%#*;myliP#wx22pP81bdK&=MB3-(qx@u!{Z#*s34y&w4hUKny<+Yaoh^uP zqb}kJ&+a_E(xZktNyND4kavH7Et^g!VWTKBkMKUc>cz9 zO|hU(eez=|ah9t5HR7Ps-6t zOnMp4o7-%}RMo(=F9|nl-ym_U=^bCBBQ2e*Mv$2YPIoVKn9jl1<*-R=4@jSKE^Gd4 zz=dMx?quMewODtDpYC`dCGPC$E>+RA&G+Mt^!oVVi-fV-7rm>R#pG9?yQs!O9}pv| z<{R!$5MsByaH4d-6FgLy7D|u8k?f!47$)!`C3^rRc!_ZaGr}fP9TvuLwcOVZ z)i6pjh$bg$)keP2+EMcp@3r9R^}}W@u}!Zw7ujKHxw1+cgAF$Q;8_Hw=lc&Vp0C{@ z%JI>_;y;o4zqA{ThVl}n^&!x_@(BPMT z!d=O5;9Ro`$!ysaGkPkS-;nT$N;(`de^OsO4{Kciviu$M>2abk8IM>qrMz+J|N64) zb)P{_sE*-i2mq8kXgdazK!TNwm85mSDJEl8TfRD$-Xl*xA&P~`{g|cq#4!X zF}<08s|0@b2t#L4^gaSimF2pgqbQvC!!%WW_~>>K$QE^7vvJfsnPD}oDgIYfr;=zP;=Xc*llhMe{*L0>|?g|I>_&3qoC5*a3){nY1?JYJ#FDp?N+KfTN3Nk*o z!utoxRaOOKr6_p^A1?8O^Pvag{Vegx&8NGXeN{jA@6eN623+ZqxJm4yGdng*idTi` z@^=Yz-UAs_fY!8)k_RXp=z~2G|9+T+w2_6b4q3s#Z;}H-x7CRpczciT1epWCC41G< zmokLO&vNF_DtM^q)S!7i#o^X;QaOd;q~^&7mi}}>P?D&^s`vW(W`Sfe_#eb;=;Ek- ze$csQZ;BkU-%?t6#%1Q*sFl6Bs5Lep1S>H%7BzW>lX(fT4BO;U%Da6H#ZCFzv_aQI zmTDTWw8{|Cs04pLT8a9d`irw{@7??xL0kIJ9jl*Z@e<6$gMkuqdbMrDViD?FIT*Yq zGL>!;F!@R;KgONR1Jx8i&}9x}IzKNZTrYDn&(_)vNTN|qmwcYqy4sFbo;RA$dGcgq zRUgyA1KBYZfW6%?4#V%s{pkB5C|z4)20;r`@1aetyDs)6&zBh+%gq%rmj3|$>b-}KqV;VK z!i@3@s+!d#^Fr(U_z~I(sMIi4t@?4>YG!lTft{Fler4qA1DE-aj1Lkq5fXvhpObA% z5PII%gnH|$22$*m90H#-bNGgrRxvnuCsujA4-97snTYmBrfcfaZz?!&S3TPoUy4}d zS5#S)CO@3}!9wydHaxAeG1eARbYQ%23$z0~becBBP5&&}DGIBASds@P0wNr4TCG5P zU)xN(nusw%e*Hzk1ekf4;Yap5j)+USu*f_Sx_HW=K;7I#ZiFqAx7EX|k5SQr-<4hw?NHqugsiYXV?F@JYdz)y$m=}u$g{+NXyS=sH^SPPIcC%PF%)O1&_ z-}YBQix?Tpg#DFO8)Ui#b$R%7gQzufJ5lO&}Mb?fI_m5$G_H;m#i}MJE%E+>&j<>jtSRXU=p<% z5Uoio(%IOC*RR?~-!FP2-+pb1Xas%BCj@s+Ihij)ljA#Y(hHv`g3@=2hdZ1_5!B~$ zYhTqnk$<7Z5<62vOdZZYdwKGSY-qKKuEcjluKZZyP+A~d5Y(_cD&YI@$jT>OF^hrt z$%#&wjR5uL(ISan2w4o-t`IMn+c`3l%woS=Fn#+H zBmi`6*Wf<)Jq?&mn3ixhI>e@-g*psqS)>0kTA`INU^l-1x17ctfdK<=@JaU!XlP(Z zpjmb8v}yg1zL`QHksqcFX+;DgG;ty$A(bYZGWY+Ve({dh6-eWt1j^1fHLza)87!cO zrZu0LiiDw~0UzcKm(__vVBeH4Vtin%n3OAFtMGoHY)3qNzGbtGTIH2Y?|x=@Y90<$ zslq&340tR2MC^{oRjdZt$ou0pd1QRR8qho4z1taW68z%%lwZq)oi>V3qE?uE8`T#b zh8*=QpDvs%Lh`!_GHFfOC~3MAxnH)lNf%Ak);fC+Ydlqw;1ZdBPl^}xhX0ywa z)kj71KP<-9SXHWRPgg>Kb-qKD5tT<)Y+uIm)xu>7?;ke-fLtjFJ#(l5x+8JyEyB@$ zD(8q3QE=W@(31(gpLRH1uX%R4A~o2EZSS2ZYRe9>p*zascah1iJTqC~%JWY+L0h)2 z1c?EH=7FPa@6be``p&J_%&k89!qe)WZQ_0@=vt&8$9eiQ(`4M2Ld;A+c)2UkO8n;kfFIoN$elIW>pw|G-Ae9j@*Hvw#OWhxU@f%#vx}7+n+BvA)osNBlyTh zr(dspg|@@bK>j(--@ZMmVQ(>&k^$7VlNcORppEp#8R@6$%a*Za6J{wU%ljC_gJAz5 zv|?DO7P^{B$Kd}e4wZgA3($S_!IjRI9SleZ99TJ{_9pAKH&|4X(uQB57PMQc_u)&J zQgu)kwPJO7a3@>a{88b_KuH1y0=}U7nqYyv_f!wzde-v`bdo9pJDGw@5N2P*7OiqX zc!+Pq_1pWLR<%c_kGB47p1oVdZJuBHK|Z4|3h8k@ew`VpTxP}s-DqIbMnFN8R8!5P z`yUw7T*Pb-8@oI_hSsjz)p4%#+-egdpgUg#X3lw~hZypkxf7#eApMu>jL?zYm(!6$q53i4qOyZw>q z3RzT2*v+~_0i~Lrmv)$0%58!zl>i`2p72`%qT^6yBSCzm%fvM{t8vY{`x>?9#}A$E zEBBj|w<(&FuZk#|N&P`jfUun!3S3D{ zG!;6N;Y>dq5powGMM$R-Alkga%o6HO{_i8Q!QHt0fI5Z!aHN9i)Juzeeg=0y>tF=ua^{df4q{kj z$UMu!6M75-&I4gNH;CL(D4RU4_5h?kvQ)@WeM5tK?zd$i%Ua&L)GBU55uw{fVR<5O z8kbFTYVELS*iLh_v@OXGhzS`yTx^o|V-fV)EHeLh=~u3^j=H(!P$@?iX)EY0?zr=U ztSPhVzIAj0NXfkTWi*_-Y6Jvkelavk70~!nsHhU6}05v%Nc&NYj9LW zS3*Eng7-_IS2U#*=NJtaCe|y=K%JJT&yk#DYD~^o>6xXAcBeSk`Lr)Xbvc}=yGudrQ#Rz?p>OBq5l}+|_co9P98^1u( z0LKVC9t*J0q8kn~%IkGQ2f72lu{ zeAxBOAF!e_MS9cR%j@ZVsX(%T{(u0f36Vy*aFgiTn=pwvhd3n~vnE09X01GdINA7% zN4zVFZ^jveA|U!%wkMtDIMFVZ4?G*c^;Tfz<`%@0ZmoqB3MV6du%p&NH;f9%PRxH6-;j_;e+)^&e*1=9> zrT`MYguE#L>k*U6`#0#Xls8-+%)9~|5`O?e|ym!?;^EszVrJxSQ!`h>t6# zrB3>qgVrYyydcl{Tx9%FWyGHCZN@v>;~YWg@2Ry9f7Gy!WM5f*+w^gK(JUh@iXcbq z4a+0HE3UP^yjuPv@2lqXZHwf&&85x=EBh-)XC4dBqVk~|Y?8GctOlHvz?X7lb8@dP zFT62njY_P<@OjP?UoFKEO;450dV?Hpc%5I72>0c$^E>^BJo`z#sM5~3Tp!Y?JK_3} zFYmNp4w*}9N=YBbAib=mj;j4@{sN0gH3rb<@J|-zdB*SWkVwiaJ2|0uv8Prpxemv< zc7b>OC87y#ZoU4mQ2q78NBfK7xXC0C{mj>+&&XN-Y(VJor=x<014LHn!B!8l36vZ> zd+eJM_ig8{loZQW;fw?lC}YB?rFH^E>dP24uSDnbdKRf)yy*XlLMtj2UfIb9hF~sg zu`W-T{^R&k;%1N&mRby|R;L;5niYO(?rTknx z=h9wQcVPXGs3!fRki-3xdw6ULAO+g6A3uxT$ssU$Gp@lYTR?5LBe=xNnge_toy zdn8i)spZ>B-0F>9F$5d0Fs`uID~yf{SuAuhTZtv0eoOrzWb!`pfRG;wDgsv+I&*-e z@CzsTzgcv?Qt2|A$hG1huzW0*^Z|RAM$zj2?Nch%5N_PebZG4F&iWqkOhX;e#RBgIwc-3H%p_8-( z*f8YHvU0L*-OHQa^UlAv()1;BD%9D58j?G!Z3EL%l>L-IhT50c$!}N2>KE}_QhTAB z)lJEey@Vz@w{7m!iZpQ@!k>RiX29*Ef|cR6%M{qnRak<8C+mFewkPsr@iPUqfiB`x z0PBS6y=41?SC$=n$+$)8@lRBzzuYR2uoc~{{avYGb>j1-CYZuY(8p+cWxT&*W+Cdl zR6l2Ui+5Hb^;UPJF!3OQh8!&^w{^js9?JMor|=uPC{FT2z%R~lW&C%^8IV)K4bCrx z;0uj+Yy;ZV<1s}^TKAli?i%IU9W;zSVLs5h$leyn_CP5mnXem|B>^Q}sqqgZd{{3{{tqQhadeUwfp5**p*7_+Idf`IE*TUdZ!fKgU0umzL6D(^4dCQ5vko z2*cRk@Mj%KPxhiqR|K$L!HYS=J@Q5^GB?|?qKvKn=|F;JXs-Ycz?}xkB;Y*^@-+-K z0!vnZpLCuq#1I1EsX)TX0*G_jWHNMs_bPRnElEmCw3_a94eC_<7FsVVaGYg%zHoU_ zX&QELR*TTCvBFfs-fkk;r<`+XIw4>xc57Eis)jOLO<;6eX1fqK42Yn4N z^VPsQ51wBp!U~02Rww$TUo5+oTe%TxKEhsa)D4>5JBc5>{w|3wtyjN4iJP;J^CmNI z(xbk}ku+}+O{4PjZVL8d+LA=hSbt0XjchwKeVp>`Y=AJ>KO?YZktYg}uU5B-<~|SU z0m%@*mb^!GKa5KH6&aOusyLztuP`$JnPgr*n2&u+MYANsUuO;;j?E_d92wm5m;`L) zhmme#_om$#B(iRZH_KdF_56_7^lu{XK50pW$goP}#yxmACrO&3!Wp$RJVmjaUiUGh z+0Gh+Qx+%6_#0XwQ)t@%)%AwiwGnyNFyp#}JnA>BU1L0U8k<^bAaU1DL&rslkJAO+ zmeuJg7XJZ9+q(9##hCRL5s!lXwd0zjb6(I z55*-3IRwickHda0`607@9UN^^c6+0%5g5fBzmwV z;nfm?A9DW&<_4U{Ci&NE;dd1dn_UEiW8@GviTJk{?dUa#vY99|&1IN9H1V<6byULyBM{JAsvQZmv?Kr4MtkPQ7^g^hNi;rLmrT zhQZ-}2CcKd_azmD+O?wa4g27y5Ey>3BZM9dwU|QnxAooq7Z_2cp7FDlZVQ+27%}Jh z83)?u6Fyny%si<+icg@Z^ah=+pVg++>c%u8g%#nfRn~=@P3DJ zP)AK#h|5a=#pXz4d_)~Q^?vhwd%Sl^e;b{fX*RuR$V4>~K}o^Qe|0gA@@lo|cF7#C zPYlN|o_#>BhE|u9jRKQ zXT#ZX5!1&a45YO~uLymy(92k@4v3g7Svx8hWhpo8(<}B-LjXOQkrxw|ra zhv_B%33VcM$?9_B`JfZA69%iEZwxHl`$X@tKNA7$wn+FKCj8AQ<2>v+mH!k zg{b+i7-zRZv6Byr;OQ#eu@zO;yUx^HjCiXA&KUj~`++YN zlaGSj4{J)?gVnYlpT{n}>Y>93lO?fhX_Ww}e@c0t!gm>$S$YP7&k4=q&TcPv8Ml<^tYZKF%Y1FUaLqOPu_8Agjq_zk_hZ)XNfK&wCBw&o6}w9F%M z_C#k0g+1u++E*uD6ZFZ%g1VV`Fu(CltxPjG`<1I2{!e)Z0?YQ&ABR^xkblMunk*E! zCYBD|DvS!MS$S?aV)_Ai(*mTwv>_l@AbxF1Sv1ACvEgzPYqfV2Dm39EJmum}_ zcJ`ZHmOpw9t-qC+6cxbgE(yCS)@**#VJ8Mv+bMa$7oJ)hR_D-u(n{Bc{Dg4U*&ccVs+_~cw5v#_9(lDijV4+@M=qYPcgg(x_bAX<#!J+10xeOcl4uj z`Bz8`Pt0GZKZh|t{5Cn~GK5npMqR~_WrJYD@s;|Y?{d!LwGZUj_f=m6d^mRV4fCF_ z?<%M8u1y0)d5oIyrZoX%(-w6zo1Wl%n?-+#;4*o)orqCohLsX$hl9cBmnNH+HY=6( zPoS5Nf!o4~A6Y#A^yl-!8`imyn{GpZ{jVsUvNGUsqW*TNP1h+hYD=F64PxUDI=Ow`RQelXbcs+GxF z@Q-SwAN-(x1D;Xc)n%FJX87_Js7L(cgBjR3oYUS*z;08O(x5w|XsG3^)fznDA5XI% z^d>on$`(jHLp)y5Am8QYe{P@g6QJf5r1@d{?g*hSo7(SqkC^XwWt#6QC^PARe|1Xd zO8T_!FzDE<^cZ9-z+p^%oAcH|-wz^#-;Jmnw#e$PVmUNN#D9Fa!(cob68gMRuF&mn z)K74WQ*4N`ADL4QVYd-G_8};_vvtvJhja-l%+>+lZjN(#qWwOVSOPIOHGuy=*4{cQ z>aOb>r#q!Yx&$NzlpKZ-Nf{(WBnBiErQ2ahNo6SM4oL|SX@(H#Zl$|xW{81-cf79a zzVGK<&;7@{e(U!Ki?!f;zOm2VpR@NqpS@2RM{?2{FET2>Gv>+#TJKuH^j&5%(}F+0 z5iyFQ?k*Y1Pr228F{G;BYw_%R!B$wTo|N#JW^-03j~igw7Ir3d;Fkk_wRF`pii$`M z(KscdXAtG19B_pxCMMVKDOlcw-twJ}rqs{W6Xd(Q^HXv&^1~*pXmdvEuQrz$*f ziOGFc@Le)zii@@&hcM^@c_K5~Zp#{r6x4Sd#M5)dyszxG`a|%~ET3{Tr{rdz$-T^F z3&nb(bz169-eK3>907PQB~W}D!q77_j>r>p9>3BHfdo)2bM}|4Y8}86d2M*VA97TE zgKvTIhX*Xpr(~nhbRBhhl0C;PHCNYan>LibDK?sIUjDis7ybSA?La#VqeKlqUyhGaXZ(Vr)>S>w~ z(lrkMjFA5^RJbl3%UD)4TpQ0$keqc!>`mY^Y3J!4o;?A>u=LZyp8xQ*jBky2lExDQ z6bxStEd1)*%=J0KNX2(Qo4}mjQ0G42eVY8sl^t3SMAU_ud^44O?JP7rz71*}e%B+ThAqpdLMUcH zLzXo&(fZ!*<_0wnYkMLu?nJCHPjtR|wtZX`%j#TQE@h6o2>pugZQmk~^)q-*pxu|Q ze4yi0G|A!Dnu(BdzHXjr*?Hx*ot>B{nPFbnWBq`K<0xR{dVLOV50Q(4Yi6Fpf?EJk1>5s7$*#kqFk6i)Q zT)E7%H*3lJx*G}Lxrp@&Q@=&pLCb)5vfqHW%8L&oNO!3oSctJ6mIs!SmWD@rW1ei_ z@3g<(_WR-5bcZ5`7caiiAlfzIR<1PQxbi6pQF%5s?Uf(8}lT z6^Y|`1h#lrdYKQO5+I30C~k2*G23(y;D5!a3ftw5+ZKgqD+SRP;FgfuZ54@s6AW%1 zec!0KZFk|OXkrXP^j%&R*zi)@ zUTri4YqS3S1B=W+Xwmnzt8tbk@x_;)L*Mn^zQDNAre4CvdhPVb3L}a^DbhL3OK@bAVwNs+ zbKmVAx^wle%Ap@1BlpRrE(!G=5B#d~XTZsPn9XJodGXtd*rRweU;$w7^IH8BI`Y@t zjqmSF>9x@K(_VPqQ3CVx`@nhXvSu;e2Mys44l&JPRDjljZo^W0C`STDM^lYVKQ~$D zMR2^oYgozDr(JG=>e`V?UnMojeYhX)Ead*Q#gnCa#CrVdh<2{JNx!zww72)e$bQBp z+Ue8t^(HrS=nZk?QjfmPrDr%VrN>afM&83HI)4iR<1z7Av9}~9RxNCo>4VY_nD5~K z4#bsKu6+~0?HqD%KSJ$~GD5Rl--fl8EA+B>dJ7KrC%~0nl{zQa>!iiL#_8)zGEBZ& zKVjSmuhS!1kfDEUulQBB(m0D5t@7+OP#dyzuCYABleB%E`E`-j>HH`>r*e?O-|7d2 zKb0_BHbq)4=6Z0`DE%)$#t!D@{-~wY4c0ILE zE%YUYS;HhCs-gL&7`;C{A|}YTPE_n|TS(qf$={`aa$h+PR-DP6+&=wW*y=`QwKTtw zug>GOzkk0jM<&0R9xlq>eHK#rp!dq{m3C{HR;PSGi(qvY^t)IIm;k#rb8D(Dju$I~ zz8GJc-Izt;@E8UaC6Nhqxdh%$NZ7L1`P@^y0A=ABrL!#{M|M4}}i zF(2)fYy^awz7N@9Hq6dBMW4K%?7t$M1o?7E*O`G~23d!TVU)i`q=6h!1 zRCcdlQWczHSsBN~Vc`5GRrOLb$5c?wANQq>ZeDh}^@;z#dI8)|<%S2-Gf060Gk_LY zVB(Yu1j5t5%j_`?yJ37Jk;z7{{4rtjcYRkRf^uD+CK_32{r7=`E z9YTngG;PoABJ0M`xF(_zqAL;mUCSDat(Xa6}!N+|14*%9GHtKCMve#!ZjE861dY5GV#<2?>JEWzsE*AWsf%lb&u24Ux zm?aFb`%%|PW82gdoc^qBx>>Y#TbrQN6YH?nkCn_HsHUy=d+JUFz9G){gMeRRC&d1=5O*ej~Shw;*N}cJeClz6eQI{8qfWi zECl)0445s(!MnL>UH`8)P01am65vK&@@c$nj5$USZi|$&gDc;E#IBg{sBb^r$7i3 zc**T#LpysgKs;euDes}NY0(HqkM@&G@W6I%_Mo;di?9>sm5`naA;jW);^wVi19-;$ zn<300K2Lq`Htl_|ycSU_dd|S`>?UdBY&TmMtknp%0=1Dhu3WtlwKBaq68cQ3N1{Qg znBUXXw>^%{SNEA5asYhp~Qm$?uBM-LqSD2usI=T}1my$lcIB z$yr-_0)(@mTvusv6}nCe4!-d~>yKf`-r-q{Fl^eplm14BgYUl$R8cBrMt4oV?Ro$$ zo<1B|c^$s}fapWe4LiP>{fwKbbI+r=m%Wn68zHvJeI2&xTL}eRdQ}?4!3Igf&-3(! zwjLelSDpB}7llQ=0KX=^=ssWH5c|ZkOQ%2bA#K=p8Upo7W`B;WElv~#3(Coqx~@ld zi#V?J--kL{>tUW#e<_03WT|WGy@`%Pk*vJmhAJzcw9Y-FzGN6DBB%{PA!_a~{4Eyi zC3tUOz(872zOF4G9oM3AuMx4xPCVuJ&l5#nVt1-V@|?*f5RdO_ zs6Aa=Di9JYX8$0zgVwA3_XCcciF{hV?he6Csu!_u|JO$-<=z&k#zv=y%?nKRMgz~6 zD1K{*n|DSvvRU|UPrO?x0e|}a_?t=fzpnph*x$cyfLLGs=H`kX{5pznpW)Nct+OBF zL@g&{C1*bdM?l(Dq<@L&A8GwdXcD&tj>8mRY28;yr3u{@p3Q*>JGdi=w<)9SeW89& zlMg>7=(g}Xg|#jM86W;zs{aTV*E%N@E2Y-O``=u~O*){hgOoK2z<~dkQF<#B7*2et zt|J|FhuFLv$P1jBhLhX0iO$-q=E|kT^?iswP2&&P2VW}d=|AtLZXtGhi+GEAnG*MQ1&^1dC`t1K`AlOi zBl+9zIsC22|8s`g0z$ABZXj&3@AJSRB)+a`Iqox;^65LR@0{)XF*IfW*8cx7Nd7K< zm-E(T(#zP~t+f^3R9*3)6zSRb!SNw|;~#We3=+@O`xv3{3@LQx!wsQg1xY6-#SMOa z>Q3C-hJfUGCXM8!e&R&O(f{)8xAE0F1DVl7dV|?|lF0W=>Iu&EMcPN}h@#n-|1zil zOA1jDL0>Gl-X&SYlFu`LFx?ro_brWW$LRUm%>^ zk?5rNJoHEts=trP2_Ac(zzLQMHW5?(U+?-yHMswfSvWgiY(G%VULBzDm$3RTbFZXn zmvjEdO61y((|LQDXw*o7a=f(58Vdw|$W9LbATTg!Cl8=HNr4G0Ss_7;0j~AgS@q@;^8K+vJ*mWh6Qd=?n+B|bDGcl9 zt8J=DP;Y z!HfVA(BMypsm@pS7y%_;vUA3kpO;?Zj?4wwI}t%{y0m?nim1%Be>Z4UY5i-EDqSWG6pjo-Fu`u&0If}KP-Z;Pu*9N z#9Nj2VL27ImyDCI1gOnd$VU2>I$OQ!+OTdicF@Sif7U4sgRb{j=St!sfS@LmX;6T3 zT2D+jiZtl=ugYTvp=Xa;@u?MXQsQz=0U8v(YNZ&-|83z*@NO*Z*ljVrnZN?Oc$QAz zUykPgWm`dS2b4y!PWW${O|BxmM!N@lz7BM<0o+zyC(eN_my`rUrd2f0Wv_gvoszJD zZBV!^^dQCMLh8Y-XYbKCym2(p;;GzwcJ9FnXyHV5G`7fx>-Fl-k8M?Ua{)IPng!cw z^T6_hoyilbo0IiLuB@BBWU=tlJ#pk;5z|t;y^IynFjA_^_!~*n;yX< zYjt4YQ3xG7zNpWpxn)?AF;_L!vQLc95Kp1^;}j_JLl03X9a{hT${46zkET_}J)1MH zyUU9#4=xV#ti(ep$((_CMbQ&M99lm-L8_1R&iF&W`8DiMU5~`%Z!T_6axn8o5EgP? z8o3PaJcvEtNMFAvsBlm_oken3YizWH6&YSa)-^tjMYRgr^HbJXcBV+PahcccF`+|! z=B?{$A0{i{Ukix9GR4+<*QCU}+{T)}@{^a8%lgf&e`W#%4Whe#r|Az;&g<7UuHJ4=?^=w_~C@r!u5w2VeNrG;&%^MY^zc&wby zWg~AmP@R=4Yu;@ZJF{z%bhJ17uU(bF?(+{w03^M@leAd)G=t-Is(KEmmy=5wy+b@hG zB1=u?&Mk}lt7hDn(inH|tUEj?3}kkbGZ`j>wm#PjsvrRpX%bgkAoWK5|)F0 zcaic0Si~On_=mb8!u7cRj9DBf&Ef{>Xjw?Mf-APWP?GFDLwCO&INc+7S~S&6aPm%;8T3wImadeuquL5l@?fyK;r`-!oM+CRQTR8~-)Br-&_ zc!AE&b15vo=+H60`D)IH5{-Mf3XnEot`T)$1R_zUx@BxhQwQsb;v=`IKlIVa+C9x>p0; zd3!hqJ~%~+U1mG(T~K9f4bJUJgNy7`G@M_?P=eOA+^^EtC_|kFs z1TGZ^=W+OfpgjGrZ!HAJm1388&#Oo&|L?Ir#qL8=3AzOO7Kb=l^ zs_CkX3`VEF%*v9~IZRTV?-;}Jgbwc>b|k9`oRtxvb1z-`h69sC7P z$%dNOk}PSu+1Uq72~P7UL{Y*S%5>&Gs~_3G-@spmB`qw;S_l7SH;EKPQNLVti-tjG zCK~qjm)I29C0gXof6$Hak7wzBh^I^KtT))KI^zyMtB9XS=u!Fc4v~%f@U17a%TyB> zw~VbBSfyj8uwZaEV)LfSsEj5+Yw%aw-DC?wvy)SnHq0q~OSUMKdRa2PUw*f;Rix+` zJl@K)G7PvNvAjvI#A#LZX0Lix_q45UkqL&%;OBUJ9!a`V9bg;hb8UQ$lwWb2a3F)Y zd|PwWJCq4EmHOnpRhHqw*nx3Qy6R^0pz83ZWQ1MeHuVW&_T?<1&Zpst-qZch=Gd0C z#aL#cEp1vHP+kd!<{vWVA)6w?9sn4*toQb*Z zLR4b!i_M$W>ms+beLMT7H~KHP)0rQt21rKMy!i(gG;-<@N3s}*ayHDlkl-)2pvz~u0<>4d0A}IENH#07K>N)uni(cbR z1RrB=uWE}&g6X$4e$y7Wa+2H?g=(Y*2uaUg!`;>Umd697v-=wWHNN1KL_3Jp+c);0KVy`aw;_BQc z<|^QR_UEqwSDw?0f=n|CNr&5ma4d=TX>(}e8(cix(GoHM#`F#^(6qFuVAFfGy1;m1 z=O0k5GWvVgDZYha;nS((cxVdlE|S5R@Q~;`IT1uv@QZRTqLaeE@*M{C(tVnC!vM9% zA&CUE0-z_LdDoZYL;Y+;B~!Ql!D2k|s`aqere%`)kmEdG)fG{Vv_-mn>r?RI&h9Jp zQ{T-iQ%r^5;jZ&zNv7u0hgEMb$l8P|cLAOgNMYBe!m9t7wd17|fv)dh$r@%Wt({JnW&Ssx1XdaG~P6Ok;+(Zku3Fr*Uc}+9W$e`H?`&OhynqX>Wy}_ zgAO5l_MC>LyzKrZwgU8;+f|7qYu-0fsfRl^h+#k8QAu!VB9eNs7F4Vao&7q@)op+- zsZ@D?a_&20H5u62?c(T*G@ksPMa_vg{Rp2Ni;AK-+^%bPzZ4j4Yisb&GSGqOWbSWb)&E{&h4Xu zXz>*NcJ2xDc%$kNEjP9=PXE|<*U!IU-6b^_+#~y0I;NN9%|^1kkR-A15Tak#g zjm|4>PRn|S&&7^;Wajkamh1{HseKRAg7s6KhJVrCszA=4C%+ed}zeqcCX&!1$|btQ9L7z{z4SaeYeEzX^+fZl#=J z!PZat6#IwWvux_5bro5!hjbY8iawZLarb_t;jiM?=&!NM5*0ygeyX|Vw`CMyxL88@ z{?QG(-qP;##JKF&8Tgx&MwfVYJ_!L4%{xEFI>g{tw@4kQ$+A;Z$Eb};dxi+U-(U$i zKS43DlSb>+qfP**)tDn`lVxrqJ-C9+f(+$1>Y-N6dgpPkyxCwen+D8IB9rZoVHG&% z{l_3Y@g?e_oa6MzMrOk^Ih$Fg9jPJ%gsMC#2dy2s6*fkzQ^~h^q&TLfi;Ve&0i&*f zGM>C?{7u2rU(Bm2U!wB-*p4;@r}2l3lR8hfM>qWva^>5fS0J|fPg0wdw1+s;T3&Bd zbSKhN@B_hlQS}|KKSBt5qx!^>D*fisWf;|A#hOCSvV?fOwg?XtV&cdzJN_;CZu{{L z1C!QoD6rFuedBy#DE+BhX|{x3?Q2sR3vFHeFL$x}QI#)=P0RFPN$w#TXZvOqUt&Gk+V+7Pvx5jp%E|c&`A}>K=w(AhCQId^YADKM zW{jxdKpGN1O-kU~eg61v-3vm^jgOmdgBL3nL1!DZn@?1x$BUtvos{m=7Nf(c)IwK$ zoX*+Vuh+-IG(R`DbOQzIl&Wzm5OQ%Q&Ku<0CoHFFhhlUP%Z>|*g7R)Oxtcy z^b|+Mtgr@?--+5q)l@nQiCF`^-r8faX=a>AVJD1SX;Fy@wi2XT133wt{m^sEBwa|>W2)FKmD!w}-}7zO5PGlm)M>w8}n zD8|8f&yJ{GZ&c5-YaQN}nkz)dXcG_J!Q{_|S^z+jg8-N(~JfCaw*9byW=yzt_@F(N## zX=rRb4@HI`G6Sr~MbrmFhAAgyT|LG5oqufgCdd19vQK5=REBl>b z@FqGxkEY)Y6;)<47%O648tgF9EX(zPj4`bijM}ci%;QJktmEyIr=kMD@$qfV%4!VkTQoJUSWA(4Vg4KX6E8JqmGRn|RfY3asRoK~=Qq$MkQ$ zeP!u8u(@{fEV`dsjLGjo@4R#_0}8MknR4)L1zn0>`ISSkT5GvaCAxS(!*l@Y@fS4b zBlPuOV&>AZl^^5Y2Jsh12_E!Wg>HCb3au&|W8i~>hsFtA#M|yCx@}!c#0bfxmr#ZA z$V7L_m%%XI5s<+1naXg3O@6&hx)Bnv=o7ii;X&)t<@G0>d&c~rmPc^2sp0P6K7*lQ z@QB+LWUrpHMeqcd7GliDIh*%abSB`0%^6OANygF}^;SKx(4dPYV)Z^^UsLL(O9gSx zf7F))Y@ciz*kl!RVhAT$5`iGD3j(rIV0QV%EfkO;2Mqd^5%~Dr?bX<9fZbNYh!hr{?gR_ZVx8rv^glXU^UAF-vP>OtL8a)= zgoQKmTX5$Ame945t_l8>IjxGrs%|xy`7BF5a_ql}CJwL4}WK+(LaJ#F3ZB<(6MpvO@XWa`lB~9+O zxEAkjXX=CQR^fm+IcH%*fbXniGB>i>SDE)K66p!kR=iZs@hG%0x6lmaMX<@c679z; z+<4Hn$ze1x34ea-6YF>kA1Sis)~k5y_o2%tr8reb{DMhDu$Ax{x4?Pz56j^cJn= z77nyxY^&n#T9ISV`z0R6^G0C3q1ElGKIjV}FB9vUOJ~x0>GPAuwmTXsDx1>3*KPYL zH$K@n!V*tbLpoyE#jM@tv~*AF_>*ij>8>Ec(?!w8iuwU$0j=2jF3?C76-Ux8_$&m; z^zFLb-UY7LaX-UZ&HP@M;QB48;BDOf)8Cz5Rg1+*$E>cLHRJMr*4l`)diYUCbS)m6+@MhnNh(jv71N1jiV-}ad!whXKf(g_ zr9+U)Vk~~H)qm2F!U5cuL}v2sr5GmlCURug(V5-rO5MA4Vw}uFU*2DzcW2Pp(k&6MlaN&@;M{Q?{c!Z60~NHZCAiGuA9i3cF}gRr5kGZaM{(=<~wmUWpK4It~!i=^JhL&HnwkOFqpmH3S*ctdc8N$o0M35K)+g5ppa}54n2lm z2oHD3KS^7;#jB%|+DGP)qIAC-RvC6-lJQwm1Hf8DA#Y*+sEzvG3DR$pq=%Jt(CcaN zYRB6)D_r61;76Y&bqcuQL4qfxGHL-)oDW#;{2@JHqe++k6yr2zv{nR`pcvXz?P~0E zh_?Y=YPNBPtkVsEm9MKcC1uU;|KYI{V7azbveHg?L@CVZ4r5M`7QYy$a+s>c)C~jK zQl{5eQ#-?26Zf;+CE@)+plN9NL z+6LwG@ul|T($$Nm=7Jvo;Z!Wa`*5aJ;TSdNt)@?A#(tvb=z@vLh$dGZUxEXPjS|FI zVL#2#s5gkG+1gMLnl$HIwIyo=qwHFxb)B_W@8jN#%I+Em?9bNGRD5;hTS6I65+2l+ zNe#$Cp97{X=5NKG6Q3Ly?r0E(TBpTbLl=~eK0WmxSM+s|a9h9A_#!4Cn`L}8vGbRu z+@4fAK2(SE7PepCZtQobkC4jNpR*#g&{qd#ZKXyNJRkISfHx}X3Ow!nk4%e4*E07x z-y+S%Y6fnVd|bTnI)R)e2S**0;eD_tx;5w{JtV+M+DoIo(a$=dL8|N*Z@uGMsGgmL z7Kh)kF#wR3d~OewtYq>P#6Pc&XU2T#I+kBrOK;|5qble3cBq0jGN>h_0t>PNP6ld3 zx#x~^7U`(nh43b;Z;vU#6vu0UG8M8G+Y!7NdXOKI@8=+0t9Ci__Ncw$O&s+wa!Fwm z+nWaTRQhS*iZ1o9c*3gR`maHbH-eo`8xq`MNSXCvAh`$8VO`*dR7)$#rO zkHomxe2mIM%kaqN&PsEC6Er9G$;~(R0g$UVn^lO+QRmnEKEhAHFzoJ{s;7+VR1519 zd$HX&eVK}l*1}x!xGHka?pmKzp?x)gMwl@D_;vIa!RK(c)N2$>iciIjZCQb`!I!He z16MG^7NjeN|4wmg#(oQZzN%a#EEb0)Z==&63H^j_gV1ys2*mthAuq2te zs@>826w3s-61evyqflxjV_E6WtM;^9>Xm_!Q#`Oq=L_2o0 ztC>qEyT#3%U%|IKfAgQ_C|oed{enSZt)dye)1b{?*T@>sXxPpq?BsX6^nj;~^P7mQ zaaMnvE0h*3D|DD%qry~-FxzW|mIUm+UfotKx96KrGd>=lP_!Q1&A&OE%^9$wS%i~} zsb@?P;A5ZLQD={F89o4D_c%Dhk5Qdk;&IO%0q`r4FWsjj|EAll>9$5Px<(PQbmaF(Z`nEpI z+^r0r80!Mo0wq;%Ga+bG`?}SI`Sa*HeO*-LbddwM4pMwyO}#0W*$b@+e0L~xm8h=< zsp^PXgIKH0a8MxjXaY8S(iUwM22GFT`d-a7-`8WS&Sj~DQ!T4fE8@(QMEfCY*_lhl zKQlipzov9Ui8YeQY|m~68I>C1CglJtAc}j~ky5MjeG2M4;!x*cf;Z*Fv_Cdb*@zA8}aka#fhF4OcRu zs-Ue<^Si2ZR_&+mEi39OGwf%xmZDe!TX!W(T{(?VY%o1%h#(&0Uz^Eo&ujyy`Qccy z)Zj>WinuEHa~L*CPbT@3$D6|*7W}8n4XssJ>i{vfO4UDSR$Vo%EX=uHKL#88SX@I| zd-9plHXVQQEQ79?XZ*sTS{Ka)y$K*V_ch(GMD;~@19Fj7E^$WuM4pI@g8D*Sf+bl$CR9KCv*8A~APsTnUa5Q$p8C6;Kbiu?^9uEw0#dQIrw)j^9cv#8E@ym&wZMO8> zP=>~@V)|;PC8hAQiybAmV4s2fXP@-^#%cj`%CRG-TQWV*68qk(AVK_qT79=*^aLH# zSfv;*(uneV$WGo+jX>6M?36IS_Aib>`ju1|_xKfPJzL;E)_J;U)4fwnE_Gm+41o6YqqBSrenvj$~!=-@4L7n&I z9w_zxa`B${>=xuPx9FF7DZed~Bq(Y4`|aECms~3i>)L+YqC6tBqS1+=UNYUdzMt(s zEc+yhqi5B%V=I5Ts^e&X(g#>zib2uCU-;jJ3QBM>YxQaBq#QnPBH%Iudr}{Npq3lR z^PqHvJo-dzUaa3w+rH}uj_EK)1{eGN_NfQ3oSGi zUPsq$LjXT~x?dpu-*XsXsaQPU>ee{5?WR_(fo?G5(YoZV7p>4~DfTfob930-tZVV=eT-SOBGW3&Dr*h3UASb)CIa^&Xx!NueV4{lajR#8&o0;Tuhdw6Z z_HYIKCuwuN&c+0-SKjQa5eE(t_K^_8Gjlzs1iSsfC+U;K z_vRbqHwzxc{Prj|KE~r%kZTOf0}4sKmXwHho5q?f$SQZrBAO z{0rDRrLI4HQKUa&LjhYwY!fbpt+#14rhm%c!Rlc_*sNlr3(@!7-0Wm#IQP)VG)k({ z!%2furj_Tk*M23n1p3VfxqVZpUYDqNyxwF28Pf)}O9LZ1-aYP5xSR~oI3H0xeLNb1 zp1>)!o@}vZ8bADIUrgL?PE)HaC2YM@Era;*Vnp&a8qt z|9nMz827B4^U*-I2&&0y=}83mqQ*z%*Ec%Yz>%r*BZ!RY0`ptF%7-0I?0RhH%lu%x z=^H+IhA^t_XqV^>CcFT1KJ<_x9MB;)oJw8Zm`-y(<~~k)Ab(`HGz{02WqRlkbA-u1 zz)kuX(Y^O!{C%A=54-ABgMBuI*-d)Ev?A#IwzTh^6HH`F zhEXO!p<7nVfSLha4Gyoe5B+za45^hi~(K5Wdo~`GWsY zzK+pl`JII5&4STX%dRtP#Cedp7~#-6r#n@{Ik|0z0Tz^RT%JlUMa1z0uJ_}q6z0jE zD-RzbByAw5J<-*j=?D6-5SSvV8#&_uF%`=Q3$~VE%o*(8#JYT@Xh!ztmzGb}aByAg*|wd6;zP~>h(xb0kq zM`Ea?$7UZayU%apo0@#jrlBIejLVj|<$R3|`#2rjRHn)7PT$*6vyCHW)-B}1KUDE4pQmJ(QdNV#KuE z*vPXIi9NBY@`VsaX6gH=#-WcIxh1oCHqiWvs-xwyN9KB|)0Sjf1_4>ZoP8uX0M{yr zJ7U0X7A?d$*;&VUM{S)p3ySAe1C^cB@+8N$$c_%*u73Q{w}RvSKVfhUd16>*%oTo^ z#*LxK$D9MnxW)7A*hN1nt*udhu*#2kP%BPSr2xul@jG8fIJ*F*qkp_c68!k8!~2?e zKtSl^!RtvS?19=a{0htyvc^l@?c}JYgRpasfNN424;ShsjP+V$^`Vu-HKW)C{jtgG zl5EpmsZg<3&#`-i4P$Fg_Kc$Oo_mDz%f{7T%Z;^x$zJ<)@S}HeSq;Zydz-n3>4&XE z>>l+WhJ}7htJG@|DmiolgJC+UAsmN}n0rPG49n4?GSttTek!(s1CnDJ_7A#gDw_Kb z+9u&5#0x&E#!Fy{0TqzKv2cgbvkHQPfWU#@UTl;EJ!ey*{A&QhwUsNrYGtQ z)S6?>2iKn(WtMJ##tWnTJ9&SX2y&U$^@b3p7<~&Vw8E$r5M=YvTwrhnjQH#@Hd(7e?NGMyh12^PrC_Bt z?34uL_eSpMntuUjC`+F5xgZ{i+Sa{MgDoIt$(q+}%JKG5T=$Pv*EPixM$2xYf((P# znIDnQ_J64A73sQ%Hi0KeXTDEfwDGgQqwpE7-wbr$93XN!KPI=_M-1b6wH)7FV#t)k zS6>ihpRWTaTxZIzD*q6Jf_A3gw&jxyr(V)m=|6sWv>+dbcXRgM=Q%s8B-QfpV)FH? z@n<`-hzG|uHqR1W-(8!GIady5QeX5muD7MxXOitbSYx!@r5bB}%JZ1#>Y4?CJ%I*D zkbyq8S;`-7C5G%(^9Pl|FGyzA`=+q{b@}m_#1dS8Hw^o7Y!;NVB8+8hA0 zs%ge*$Q6Aq{dvtR5f=@<9qO{VvJ;0pWpS+z%XmxWOoi}=KO|`sZ}6Ct^AFgAj<&-& zaLWA`w%bxG`xVal?_y?hVvc|pt|psQvX=HewM}_!M-zxUIP#vY6vdN0fRdN^9mgMm zxQG9TviAyWvU~PG>Aea{?@CorkX}LurFW$TP(XU`kkBE36cG^VAW{XS_bx$tr1uVy zUJ`o9$@lI3-}~(6IX5R4$pyT#);nv~{MO8@nPHZDaOGqJT$|wdb5uLu3oIfPlE2%Y zr6imw<2pw!QWkfJTk;%A?a)G2OZQP0eXbF~>a&;BeaFgI`)(jS5VDW0o3-N!ILAv{ zSwe}@2NH=^kkA0yxvmt_^s)%1`sVo}!iZ+2E}5-%LAYTi-bVz?Wzvo-U@z8Q5xYeE z&g&b_wa~@)7Fo!4L?2t%UFa3JmJnXz%)Okc-{S@+pNE(_q5i}*Z{y>`;#bqIXBtev zk@kNnlH%5Vh-N3NaS`oCJC`jD`<$Ief5V~7f`|0C6Qi-f{(wn?t3c$#GH~XBk$QDC z>r$uqEAUi@{9e%Qk3UI35a+q;hBDvrSIb_v4;c|DVE7NEP$8{6Y-Z^lxitixC0_b0 zwi#o=(y+HVsCn2&lZLhvZ7mBT&pLTpvx@ar?a~lGI|G66bi0MCy$9_+et*~G=EZmh zMx5$&Gt@mnMT0Up+duROV-6Ur1eSg-(rbPIXDkY~C#d;0Kf*}AnKQSp5S(5grd+~G zj~nc6Phxrah4lALHUC}AwI}IefAl)3!__x69?tu(of)_l2b{=l7^{rIyY)6ON664i z*leANM&#v!8GSRlr-{n4GZ??dZab>;CH+w&17rW}v^txN2FDmH;2#5kOavhbh|T69qZ5^iZ!LZ3mMLfQW5X4lZd3>Na_U1n zjS2>Gifg=sVA9V_sh`V90O7w&?9sR(blyqjF-p^egt>%OSL@<33)i1-2^eX}8S7#Bn@g_REgn zvVCSGyQy_vx5TjV<;K3g8T&9c!2B)G*G_s(>D>S>oeY5D3d**T*`gv_^&&*W{SVC< z-t8#97l*jrl+{|4&$kX-r1S)|q6Aa>xJ;*7+l9mL&##TQ5|=muo|bKPO8s3@Cb|6p zi9p_7NTh!u0a?dGYdP5c;UcEa=^x$P!Evw)cdv%rE#q9>DsFei?kia}a=IkV#z1a2 zR$rPwspxNj-asi-d0sJ_-?Bw6Jcdgjy$vK``?x?!gQ}Lnt$dmY(S58|?Q(SWpIQL9 zOEFwdRN&d<#jvgnSpta~FprgF$mQMaHXG;NXc7Pi4Kcx^pugdDUs-z9eYOXSvyX>v zkB};lCo43uqvlY+)@tw64j;MXlpOHZt|Z|!rbb*87(po*?TQ`Fs!&!X})T8AYnO?EZ=Gva@@9~ZKs zQiWQ{FgDi=_G>4_0!`F{qPZW5cc^2lsY(<_uD5PbLg zw(FI_U`LHRM~%%dPk9gvdm-{>Eys@v-1o6}*X!I;;c5f z)bG!}lH^BXZ;TZ=U~Ru>`@nA3#{ zw1A6qu?XD_@iqHE3-}D01EXdcxVoO+;;ko|yhiI}H4&}nAPnz|0sWE-u z4+?;E{;p%@k;7@L3~z4l;zPyDcy2DF-!uE}^PiVk=1eBD7RSFti1)tm;S+b^KHnQ^ zWWij`MuvoCz$wypAH510vQ1aL+fZ@CCA>T&4*t7c)@=ez^+j~Nnn@x~k_o#isyM_? zu#JzA(&)0}C=#S?ZO=Se?o<&OwD9)OvXYS|Fz-;9iCX3-orHX>pjs_4phvP&Ey=ZfcvX_i;OXnKZo8L?$tXeXx?!m;c?q19(d}ST-20CAXYDxd zcJAoH>~p<&Ym9SRyUVIW$SPZ75Nd|23IKGw7Zt#<@WlN?11?p%UT)h$b2a;5yC0Bv z!mm1`IvLdJwQ)W;+XVJnv;0F|pm_+$ZSA}ef_ZHoj>{jFANBSX1VvHrn8*;2r?TL< zELtR7S$KzMQ2(58Z5vD?Ur>l}&DeL3h~JY3by++KIf$5~I)bU~6(`>6zWv)cF=6(k z!&l_(Zjyw#X>OhI%zWFi|ATAk3ul}qC(5!|v84OGiNecG^*m`zy9;?hSxkSlqL*cg zkKNxtw(7^rDmxW_=)cMsR5|^u)K_=&zPRsR_tV5{>}q6jua+&gkgnKpNL^J4Hjbw9 zP|ca7W_@9N)m$};PsFDCi0bC<9B}$>#PIF-%|&&RdokH7^fiyeC5q;~jJcl+)8m7J z>j2{fiuhy@*)Q1v>SyRyOGxV$f%e1aNphK-{CZ@Mr1zBhFFZ-!U{R6^aV}^1wX6CEH1T-Xl+vQp_!N4o4@-2pr$>L~dhGGSug=Jaj(- zs&cPiF!3pA8*;Cw#`aO}cAf5bz^c;O)?HTGS${wts)8#7c?`x9zw2Q1?}crg!lzYa zglxBaf~I5ID|iAo~yiDndUhrb?zLSkS)dJ-%m}5 zBM=S39+)Zuk2qv-$oRzh3$w9$koW57gpkoE9pQ~o#FspIlty|rZ=W50^*0GZ_;V#- zmqX^m!OOK*P$S&*-;a`JM`tgT{WIP<;xIMB$tn#rWDL)Np8_c+{mhceK}3J5|4f!% z^GffFpPo!fnIJSle;Edi7CK03RwT*8xQ+v;B-{sCy0LLx@n&RD?)g&gp5SUViEqby zLbq^%NVkpvD);TnjTEywOaFVWMxl*W)SBp;U}mW9OKwzdEGi)uBwIyz zeH4doL4VHB^~SZalX;RGQ6}{InufK7<#hX6HehKmeL$E!3$M z*Itu_u+pL{Eu4$toQBZ~OthTn*h8_|AjR{gB`-k+H3bivK$2U+5y?kc5oYu?5xYZR?7>_ASGB zETQ|}J4%jw$nP_&s*6l}jyssnhG%?Ke}u7iS6`=f5yESA@tehRYkgEOA1wDau`^-U zf5x=m+hvjX&3^5E7*bG*MxrLB{`?A4c)Pm&^}_FCjJTzMq)L2Q1_4rL1J^4-!@Nlktok!3DVu zdb>EMcse0kh{M*!6?HUxnKIOGRgC#+{nKSAQt;Dt;G}F3)s6{`F!cf=#AjWU_7frS zh3s_a(XR}J4GLMNP0W=E&O3;uxGH>B@mrHpfzE2`S=Es*{#G+hzE@Kfk+|zTcJn0b zEpE(80Gs>X6kqqw$M>DcmhU{h$h{$dQm*>HtE*SP`rczg03C-5>asc#j=*nCt>y?B z)Y(2!#^%k>tB}a*{b%PG^GA4P=rdo;m4kt8r_A%fr@i1i!SnODidObum1@%!qy1y3Z=E$vyWuqCCsivgwe${iRf!I*@dnRcCfz-5NG`)^pZy;I#zPZ+7kw6lQ-?^1 z@eCx=F$C7;UKD=z8u5rx)V8zuBQgxyHNjPc@NRVi`H(Df)?&RVS7+kNauQ*0(8<8w zE-(tl6fKL}LM|x`5cZCF^|+S$^p4qe{OHedAgvnw+P)arb$4ShXzL(rjCvv(NFLxNoI-PF-{@t8xHUm5n6KiD(6AKjw*=std2mh3MjJml4Q z*dM`}uBE`oF*{vPW$xTs%x4*azSs|r7)43_`x-ofKl3J-O>4L8`zhJbwYxVh{g@w`2FD!T1rq39WNl_=+CGC%$`8_#Zn9`z=P$;wh#TZ)T51udf0WYK?- zVzU=><@_lv^dDqkw!PCR5urt>g~eau=M;kZRul60F0`$EXd79|yJE|Ma|;ARw+I3f z)86#wHHYLO;wN*P%G^}NnVPmm0k2(^t4EwCcr>jko~SL>pa!B!xk8CSA4~)u>=Z5Hxjkf0ztwTfRUb?} znOiVYFD*WXCuQ=nCPYv^z-lYn$V}!fOW;b_$z6a~=sVXg#^zMxlRa!dj_ip^ zOEJgOvn7D|U$%f=%gcj04spbKNhlh+;zOve_XL$WkKd2CQ<)v-pPL)FlYsrAC^npk zh+Yr#(PXzt{Gu{~J;}$d`Q@B1mo@KtO4>_K#KV4P@kcpO)S@FPciuuw?-pv@6_~bi z9CfreyIi-@x|HZ!iyNp8UMZMs0fOkOlq=doQoWiKB$+4wE?YyNQGunhPuG0XRwFqW+`yzYwH80B$@Of4c`r) z1Mh%8M4Qz(Nrvz3Qp{C-uY}Uwc<F{SX+|(8yUL_t^XPx@bcL9MLmn%7cA<#4by%`s-4SSFKWS_Fvoex&b zR$u`439C=Hi&8nU7xP`>ur9zo87zu!C+cvsOeQayCFJqxaRyH{D6JYI)VS8$@3!cw zP>AU%CJXDhMQ{afd=ZH~V(%gmZXU-rJgVCrQw~BbCBj*nmQ)cvJgbib&$Yy9TxQdu z)9%*|QMR+o;a798V~(zca6g3joI68#-U}y2*pEhQFpXC61WSxJ2r;|K`#p%ul8GZk7zZkz+O#-ml(bey zZ!Y2i!6G^QW6d`Tg(V3#;Nij0zMCis@MphOsQHKv6|<-_&mS|@m6lyt%tB{Hu70txtzZ9ZBltX9CWOj+;J) zuPPAAUaUsJ87utQzi!&u4h%Ng-%zdx%sV`(0)|c_(G_l|J|kN^x$G;0>ut3xN6{li z<0ru(w>f^XfCF__!1I$!Lb-xIAb1DaF{@o+aGibb${Bd|3Kt^SM$1&0e09|2MsjLz zEDWwYM5}HOLuHi_U%32uTFVn+{8q0aL*jDXn_E|@ZTbwTtUQ%pnq~~_I2pmjrLDl` z%!L=W$8b{Cw^|49<%t2Nuqcjc<}|e1b3EE(&u0r1WpxFfEaMg{(V~8TOB*3EZ*e+W z8@U`)*wR9=&Ng1c0-B1dWs_J5Rq%OZVomW9`v1_NNQoclAOM`qGB9+6`-w96>G~Sv zz9lk(=O+7&f8j9%G#V*icnFabqRfShP)M=L!0hEsZ_xgXda6#&1A6vKwpULTrlO6) zNg(Q1K1M>*TIzyfmIn9_NNUo~vgBZdH4y61xCOoeH3iFsisb60B8B6S6Kp=;qd!r+ za^d3{0huEvXJhws!$*ZTX(pDr@bws1Em~4pURU1s+oM}UbVkGMuljEFuYb@5#H`H~ zsOespo3XODE&yk^r9A$4DbuByol3iQ5~Q{HZC|)7ykk$(kwVfIRs${{z=~p66%n~H z+*B6d`h=C67VW@=<2s;Ea6k0A^gbcWT;9;E&YB-pTV=!fvoOnb1m*EN zZL=(RnPusI*VaBTQzanO$gZNalUKU)xc&4|8X>vtM|j8OImO{07W(F*_c51lS#I*k zTa48QJ6b$cLBI=^gfI$SyB8BI)w+wX-pEOXvaaVuuXdrbk*A5BcYz8qQPL04%i=5S zf!S;5{;e(%bRYV`A$Wp8IWH||GLaNDawpdjTlG_J(*yCMuk3DPbBRqUNiJci2@ni` zY1bBYbp+$zW~$|~gQlyQfoR-|;1zsf7`3i9(J%6;yQ_~S840wQ!ju+y-NgAK)oBJuVvPjngLbxAwjgT;< zF0Cmgaxa8LAY(0`0Z}U-CL79v*B{DsR&|{R@S`^$vf2YRLo$cdSjA%DNI#EY0q+gu z?iy}DuHBZvuHUNHliwfP4jvRz0?6-$w zDX2yLU8QCTBx)*|G?oEUver_b0zy)uvjq`X#0mTFcfCg&W{Pi8b?8+PB|s2&;IvNy z*TZtO$II!89K`r7+CROVLe7R4c3aH0fI+70;3cAs>XtyBLZxVC&6h&bGa-b`irXO< zYXSA=c1-#K8Ov?d9h;K5F?rx&>4=yM>Da5(qmWSe*|3n7_Z9I{Udt~jy@08~A_xv! zyGRbqC$otF5E65`V&4_^IPdU^u8>l0=-lT;?tNxs?R~q*wn{mh*nxT>4cqo<-tV6! z&%}0ejxikxzuoLwg5%(y;?Hkd&J&j?xuQh`r4PCfM}G$zcFby?)ooQ=+#ZM|55=(d zOZ-6ZPGdF~r~{%Me8x~1FW5>2yIk?lgcCzOz%q!d$NWiM_jZNjve5Ix?@Vq*o~n() ze#@!QX=ITt>QwnIly*tS>@ zcml{I&xp!bZJ82-4z6X!#FpO&Z0Kb5pnm?g3&EgBe+)#hVa-k1Z`DPukW2Mk=4c^j zI0G0!bGqt}d|TL*07!zb6P{haKh@sX517idr$J!~x6pK)4pcx`r4x1?xS=dE+d$w0 z@Z?8-W<%Xq;+`U&3uD2j+b_!{&%dfc5+z*R3N@aMkT^eEC>a%z_Chw#!b@vC^$Kr~ z$9F3nMB)uWHo}+Vu5DF1+tRL?iuHY*@zl$d737$oj3r05!_K1b0>!~wUE;7xT9CwsZVL|WO-RBbZTN61~myv|AQ9N zTKABAoRal2<^bxSzy{s)y$NS2 z>oi$?7JGjo+jP4(%&mJJ%W?a_LRT&i3=_RHxP})6$i7X?xJ{6mOPAbKtXlQVvUpXs zKjRV$LOxs!F$4L$YhszTZbNk4^pN4un3@MEIxo8LNf=*e4ow z-7*$^+5QpJh((MYah~{bnw+?OZ$5V*uC4f1!7_U$5d$@pYfP$x4Rm&?DV1_}Bc`QfK60lwI2_32}|_$tWKwuO&jZ zVBWM_wh?LXV7Atq<)v~U1dPV(?33LobSk^M_Uocrt`#`WPi#8~<`?=&1)MvqU2ZhA ztUo7E24?mi{vOtznt$gXa}avpK6&1U2648yG_A9Q+voa!9nuigeth1KdpXfVT#~1O z*him^{T^%kd0+fah#M3WMIW2p9xr9qdDfZPPAa>xA^ocAR@>}>Md-Z=nnH}VI5`K? zfHm>5|CT$Yzci(Udh`*Rz4bP?BewHKXr-VNY24U$U=yrh62?-G13aW(Wcm|N%V zS9muE4>jswQV4RflzEHJI&PNH1wn)Rv>LzPxGtWJK_`>W5=Rbz^V+MRjXSuY-Z&OXnnM z$IVX6dI&#NOV?2wC-v{6Y(TJ*XWPM28eB=o11x)e#$?#(+N+^_+>yGgaLbvr=p@9T zamUAYJ_dVelC82WW@HYIZZ87hXk_XDx;#5?bNH8$$JSp33M(xukt(5O>;f`zC+kFMigTqOs=Cq0Q>9D0sLF z9p^%u(6M)-jQ!&@+ko=>5dZ5w<9A`RNziS`XAtzH9dI+Z`Rb(Jx*eqX)t@j;EkPy* zE)J+!acw(oZddMnHleWM@hq0VxodDsqZMuP5ErmbzDOB>g+wgQ0H8&vRh{ubh?=CB=GD!>A-E+B_QVV zPM`(!`$+)kth90^8&Z1zjhcsM`RC$yWl|N(46j<- zH&2A}j+<7o4Z&;OxR9zzTc5()-_r2Z+@Rc{L%Ow;lOwZgeTIGLI~_UI?LwJ&kAu^9 zrNNiWmo&emmYTo-{v@f4UHqbq-!hV?t{ZGp{#Y>Uz5r$xnXM1}l=LGK9cLS)hAY7+ zqu8pNq1Vb8Z(L*xp*e%+pj{V+U5zn7>J^jQ$XT78*RA-vUURupSq(s&+z8|7WuPo zsY|6|&~z96p}ca_Wx+!$Nj8AlZ`qE#9Ifbk|{QDbhaE+o|;gQl@$*KM%aTIXkvN4`Ju)iK^ z$)p=FlfVh9I9j_#?=0r&1{B~wdeS;>DNTLYdcC0xzDl`8wGX#gK-t7&uQbli%Gf4v zx4asIpca(+C@}h#8urlGJPvNzF?S>dw){Gj;EH%Z;C$VX{Mv!toqhc)*x54vH?Bi- zX9Xqkn+=a-SHi$&*@g2TmlCF2`9*RGoK%$v(`OP1NM%O#@;iSnG7-H4SqEV(4Y`j# zItWP*gz_p4>MQX&1}}&Qw;cv(LbT~+VVBBXb|EMK{>dXBTn35fxrs9xRiJtDwQQM| zRJdcpYV-rV`;VeqP4^@-{vK0XN7(ZnI^W+A5K=qfl|_VK=8j}&SeR)tO9~}mLwYDj z+V8t=t^X^a?+SH^Xhikd5VYvjYo%4N19j2%-pwU|PPR2o;H1085sSDCO3~1s3h`4T ztvwGX-LGV3KM2R;zYM7WAG?b&8H4BIts7At*1Qt)QYlKhDL0r`Q1 z7QLfi&4#b}DX{K~96O5=*%$_m4P)`S=|v~{q&owyrBFw*0X%CrL9EE)(!Cwy@0o+ z@i2LNYQ0dDrtw%_xPfI?-PlI+&*Cel7$Y1Wt8j}6UO{H!C>H8S8QznyM|X>$7~tSK zG40&XP26*Al=;&_ zfy$-RCX#>;x_DSYAs-tN_l~^GuYdh4-ZXNQ&dSWL+UN5yLYM116iRRu&sBhZtU?0| z$0s<2dOzamAYN>_mlNW%WeDqwZE6US)#$$U$?mqrS?k5IllgZCz7HrIKiAkz)fCr3 z*jlGdR`T4atNfe@ECh-exm#lM3x^0}yaW$&F+H9nas+UipJ!F*5~nq@K2@lH8~2iy zqFAizzh`3ZFdu}J)34J&V1HvMRY$A_p=Cb}zTPl;a_qZlp?)H;WwG{FM1uPFu=Ksl z_GRBsB@5B`)hB&9^FA}{et|xOxRBg_qp6_jks9f(@)+O2F&vORqu&J$6HOq;V4TWt zxL5P9XBTI+)+rCKdtZY5JZ3HYo?O zFS{idB$n{t3B9CUgDw!Vd1RO-;gJUBcQc?;YlK)Le54r1v6#~%9pxS8iQK6#oQoZ! z@bcNijYl-qmXlR(pXa^+#rwt+Us%swg|UJrZimsqnvC{2qS0`b4B%_;1x@WY7e_u= zX}6bb@`(JNyEwduj`w_P2kP;hSQ3*r)v;_ibhvNtPU@t>6^4_A7vc-~2-+UbW=FsMB5;FY8D&5y{ivgO3&OnAt3gR;9MbQFMmIV z*?7;K6JXVym4|$mwEe87^7JU-!7EO;QRat5sIF44kkaW*(p6+B`g9W!U#XJ|K zrdFMHyYo4=@LR`R|7}k2W1ckwTYTz{*uPk=FO*pil}28~HSs1DT-+bQeI92@`ELwj6uO^TqaNB0hnbQS z%PZhVrEpVrc$S@}W@PAj{bG3k_tW{c)8*_CjliVc`Q2gNDtkHLe8d2p<7uZb^ ze$@w9;Ms0J*xEgD!v7-}TM@8cqWyR-roF(ogLs}ak!da!>t_W0lbbg+D)QU;A9x%0 z$e4a9Gn0@#-g}L0_&T)MX#X!e^~V8HFfp<9Hwlum>C~jtatRI2L^lR_s&$yp59iA= z|5Ew+?@qK&hc1FcFM69B&FC9589$1W1@tvttnn1;>JIA}A@$1cU8JY@zvpRsXOH`= z37O|0ba)bVch3h7;)f>+eX9J$2i?Poj=?TI7ilex6 z^>ysn#++6uPuFhr@;*n}ON76|CL!qYU1+D4_v5w?L~UGP%ER562j#Md`bdm6^V|?{ z$O?TC2>p<||C;lw`MZ|DR~&J&B*ficNl3V~SOd59H=#5k*b{3wZ;Cu96AKD=A)RXv7@OkU)UlfR z^Q~Tz{0b~V9YR24m-R=uAYFat9(COW+y>@Uc}3FiuNAcTD}jbwFUI`~h19VXFqrV& z^rea)w;+8(q3dWLUU^odlO)bL9(LPUO!mt~Wxy0xc4?fE%z15E7;(1P3tUHPU1xTO z0e*;6ooNRCHm(+h2$@Zds9B@~$OPh+jKyHZI9!vg!GsUK}jH21r4vKkTLJ#uV{ zEn4{c>ZB)>sUkE~FA1BhBT@!W1YfU;fF-i$Psq5bwOSxJnfQW9 z(6RkPvP$#DSY0)tCW}BzRxj-{})*8(3|~naSe3f&O9KgMg?wEPGP- zn=SM{JrL!Vh1pxl!l3?`fvYaoixj^MLi$yoY_VI(rTE4Am-!wd+gcTthH9|W-bCjl zH#fL#H>?HtaU?5C!v-{isH`Q}wGDtM=Yt4EI7K z3*c{RKFXAnGwG>0h~weFG? zCZs0}X4m|Hqmv>)y>pWkSGQH|wznFyU}U@iCpB&i6mX`D{duMOGF} z(#fBM`{F&6seE7SSLXnf;Pm4I1m)Kp`W1)!kIUbhdz4r>62A>(XRFI#UuD}&LmiN+ z%qm8G3R~2i4f-xk(;|ma!du08FIMWMd$wO>5-mmX9n9mn5e|I^pAzzjQXr@L(JTBubYjHT4B5J!{1U)%kjgKB>Y-VW1$VBBc zG9lJ@YrI&nQnQl~DhrOWK`H}NAVRb-DQcGK*oA%=4F>50h^(`%nBSJ;ex%@ZU=CB% zM!0O?*Fi{me#FM$dSN@#k(ig3HNVJmuLuvrou+IM(i)%Q!4X61*RLe2D@xHm8VM?- z{KgpfI*DCm0;H((T&3*JBmLmif6Io!W-qq5J8@sRSbgcWs%V2*Eiz?vF6HYl1xR3A z$uvz!4W<%8E}WE;EV_G)@yH;B>Exj|QMjW&6WTzI&k56Wml5_esoSdMX=DW`2erOzgK^*Va{^ab~1d52^BaRt;|0 z%0}AGKohP@%@Gt?{_2D0G+50vc|W@V@!k2k6KYY^PupS%!g{foNmNAV7C1_=qC)3| zL+2w|`SS@$I*U(m9ryLL>u8cSL`WL~v_ykxl3A|AD5mQk+Ca6*xa``EW(UX}`S|FY zzJ>RSo&8qJETB*+P^HvQ=l*I-hgZ+_p3>D?;&IGPWXO-Th?gO;8Ly|=$hg+d++Q~v zv_TlH@ecYz)SvaJEtNj^{UIA`CO4(P5#z~d6r_u%5~W*XO*x(Jwy)Rt@*81`=V8fw zgoOq!=sB3Zs^k`nnLh@1LVr_{d7?r{>%8Lr^`lDB!f@xF&DnUxnWwooPVzPnCn;uC z-sHN65bUZ@cbnYz=oE`RcLAGE#{F_~di^x)u7<*BzVh6J{-z0G@1fl!dTGg&_Pk2I z=>)}jR*hNDG)nbC7drcn~d7U+s1hKmQu z5ULC~RV9E1CK6sNwj(7+;!gbjxrPgN*XtRWA)xIs8q{1(DY#(k^ex!LgLFf}#k4=IeT%661Kz>4 z;M5o64ER1ocFN|&M*)XR`Ebbk(dXL3ZdTgO3umaxA$3Qr0yf>LtDI{?mR)yN?dj{5 z=bT=|uhdKVS=gQ3jmQa~Qn?b+k~jEj`7{%CB@@xt+VE|Z8k3+x1JddUCvdyWL*Qu| zKUO`FS*CK42Bj7p*SgY*zLo6_PDtmD>87*B&MD!$m!D;?v>p#`lop`6*?1QsgyHQM zqyUGPw0YA4;+9TL<{|s53rZmthhp`z{qOkhrbM-!sxi++Z|yVcd|gfkob2z>-K5Wm zvpNYGYR5}Iqo{D(p4?ONC_bT?)$&2UL+alJHgK&GN#Kx2g^t!K`pUv6If{bYJ`HCQ zIS1M3%LZZL>+%j9&L?U--k^?Peysgw;8{E0B27qCIK>3Hhife!yGB|JKD@xZN)9np zJmc1KqxFofmv5>nIk=RCL)v&{p&_twWzQ_Y4Cu%JVjN<<3oN93yg};^ZTYR=g0enL zuW;k4xzGigw2`||p1vUg_}`st|yDV*7s+}^(9#; zB5?y;gnT@VG92Wh?SWWeSV%-N$A`FRzVy3TF6QXchOA`LUl`q;%uR55^QYc__#@Y- zWy@*$ewX>J;Hnf=&E^I=IFl^bi28_?hG15X`hAP)6 zzwk-tt_}yS5!Hk6YO0DGrr4Hslm@48-{>I7pPxJF={V!axA1UQ0D;WINFWegb^H%s zLopKa@*SRj_2Q5$t1ACu+ww0yhhe(lBH5K=`QEaBMGixkN)>C@z-vX*-2N@;1Wl(N zH0i6VsWE$bW>?aGW{fYYWr>gKrv88;va--{!Xz%VJ|{NhBP9++Dg#_T1cbaInme^m z(;p~$cv<@&y3c`}q330iZr?4N6m;Z8_HyAFQeSA*;`2q>!=J>J`Tn0-|G5^2&EoT6 z=~*dY*Z6253fwxl4>&sInT=P6pV49?ym*6;(Nw}?|HE8PJa;+KT`hCJCslR%#Ik%$ z7&mU1fm4YT#Q~AJQ!{URMY`S0K1)~&#@cZad+Ne4OY!;F_%E1euZI)fl`kM<_GPVBIga?3~z-!!Tyqh6_eZJ++%mH0=uB+5^+ zCwsEQklN@Vvc3nICd|&{!uMqy;L2*`(6-is<^CVzmrpPh<^2oYpe2+e^)EJ&|K~L* z@${=U-4zZXpmTVY9a9v~jn4t2H1@BAT?3!~x|gDuXa9`yz;qI5q!pFBYhk?;ct}6H z(B%Jeko;#)(BWL%z|46z!slpEGt-CUD~!^KLq1jm7A?-@tz&u4{rN-{`Ar37*v4D;b3FfC|B3aNAh>9 zEk1Ss=Z&Rs_-19B*)Np4U_k>cW0XX5*Y=AC0kJCI6oxQMmJm>w7pkX}MeO5C zYf|s(Oz+_!B>7*v_21>6@T696IqLam?VdHo%bwXKgyw+749At8a+2#0w5HotUO&DO z(`S*GgT=^G5NaM`>B~~rAw3QWgdWB}s7A<(@tkguah>KzbXWPYFSt&waR(I^tUPmE zCYS)8JId6%BHDfaGiiBdY%fa{+dbqXgmRC$iVEQ98-d5#6ozx5p<3eJrt`%3_$5e1 ztbNn3)H;YsD*n0^8k8`07Vy_k985I4D-p}B>A!Zbyb`C?SjF>?q#J7kBp6~lPrgf~ zULoxzJoku3phW(KtIat{KF>y-Fs@XWwfV|}yaL^tv) zTqAp)S(`dGmF3a-#+&j(Vj_w61$Ga5d7Y~U2|zyiydpxRm|Ca8n2aHtyN#aujZc3G zWr)Hb6}9R5?)^vaN#LAgVF$^H32HWAycT)J2Aba9na{GutMh0e_rZy3nMEbe9$OFx zjQ661!_cmS=f)OCtSc^yCXVOkd|&>=7lRD{>Q6qLi(u#Q7t=5_}oXZu0RBJ*>8(Y8~Ydp{Ij;@n%b==R)%!!6uaP zYU7)Oha|zt70*fZ^WY{wapeX~6xr^FJ~9Prv;4Pchg~y-E$9r##g@(Nzj$~OF%u@g z_IvIVlfB6Dr-x=S`7WT4b!V2+kv3y?fcp6aLi*;7f>v)tw=9hAF_|331s*w-`cKrM zg}81yhDG=1F2+&l@jr}w$%Qu(uw+T_#SxaLrb z9k`tDAt0M=+U^GePep1{fg@JCU$^TR4&Z(H#oxK`GU3ZU29!R1jVesszUCsezY?(% zKl;GUWCyL)*gWUTOhWXOq5MGFvnYO}ACiNa(%5h54f!T@XuFf3B4Ekw%RWd-Rsl&9|)PKq<+3@6eJ~zSut^Ad;XI~HD@f& zFWh$1&XOK1PWbvm7vaJx?PJr#=FWW_SdKDpJq#?4ZMt20kJ=Nt)@yuTBvXEGDF5q= zP{Qr;ZP%31L9__KeqzLg{P54-VXOI>6dPmL%#Q^7!zO)XCCg`}8{ZZAmH&apHKNG> zoj^>A^Bg1o%0;XXSGa?X@o2h6Csuu&ek0vy?4N;kG|h9-N?23=^&8(?SAK;qMlJ`i zsP$U!+pt%wI~kFxBU*^{3dfJ(_DBn_`==rG?>>%CL9^tXcF1h%U7E1;Zl{CP-{tN_ zOYq~=Po0waBsc}v3)zeK5}ESLI!ZqRBFD4*5I-z}``Ayn6kg*#xYyrunL|G71DByv zPV6^T2#MHK;P|rvPHME3avM>yC>Xx=R)znavEhuI_F*cEl+4;-zuusi%9xZQ*8hkb z2ILY#C99+HiwSl&4Hf80>2MZ=975x*zP>Si{^YhUnsKm8l!@Od`^xTZ={gl&1HrD| z(w-n~Mt@l3aP;NGL|- z@ov5mP{gC~rXbdhpIvh;(bJt-QT~8lRz^}U44a+~dh$D8ay8_V<<%Wx5*@3ZC6>c` zQ17;yJ2WiRB?D8Tv-r&Gdv+cWpr6l~$VPk(_|H+IoBE41&gpRJw@S%z9{g;KVTOYv zrJX_(aercnG4(&bGHEY!ic^nrV! z9+LkdnU77aJ{-fi7g&O=$Y0~vf?zmI?l6j+oqu52*!|+O&#VSm{&=E1FzXNfhJ}>xqve z+-x|y1AKLL3 zxFa3FeJkV8y)AB;qezIpyl7L#G<{RAXNgJsy+(~&PkK?D;Qw7GOEqxOxrqkWN5P`# zJ%N2oJ^EFRre-q9@ABtn5)EmhF-Wk>Mcl~nuZi6?BxF+w72S^5?C}j^Y8!t zRVj%c94CcnW%S|oWG+0Xa%fTlUKWr`lQkt~PS`{mNxZ0S3o|_%N?j|SQwP!PRUKQv z;+cblkV_rc(GHz=xKR|67>ddFOg~zrkM$Cij4fe*mF@lR<&MC@v`2*;?j=PilqICh zUd+JE2cK;raHw1<$6qV^iJ{!%(G3<{qboM=I-Pg%z7?cQPzj1H#P5H#0Pbh(uZTnI zTgqOZf3fhFhyTPmD6lW&oO!jIWnxJs%>Gbr$dIDmwhnFatlQ)gtsWQ;R8Gus8q3te?DF_nd7t^ zl?G0K(3ec<>QFZKz)|)ly8`ao4{iiYw3L~2Ps#938}al%hep|9NhlRsoTvnNEx!s2 zzhCeLQ?8zjfw`l7cT$ZV`S>fFevv4F(HDJ60&4>idiq;RZ&A0H1E(dGvl;c=cY`e` zh&sgaHs{~q`=6@epR{Q>0h90vx5wX%{?o^Nr;5Q-f-52y?f&&d|0z>2Ik_Ik_#q5h zT!o@{G?hOtm8|`yyUcbwm6N699kBm91C?JHr0NwnnibWMTE2KRbvTNi>O5}U=T?{g zy1f+m_aVFwC?1QNXk2>Ka{hvas{Itb1M@MD^ZgcZFp)Lr@;36SR#-Ogn4v~I;8$8q zaogoe8+U;Fy{v9;gwU?+^{`G)j@Jbq+&)V-9mQaAbnxAG&P9UX07=zgGp3H1I|IeMoa1~1;~Z$sPhVT zH2?16PM1A+{h<;#@C*tMxR}jAi8U0mf*@usH|eZuoe7Q%G*|Dtk<>*u7>AwX!fS_WRT%vH^)Ar z!k}+-ZL8P6$J%h)C+$F==CfP()K8Z~uPNDr7NwNFLkA8t!N=3K{1V+Uc@A}%1+c(cCHm1sYkl>5; zK^&h`i2YS=$BivwxDW&2A9`4E>$4^~H?qNYTPg%f-mYv;%8`^jFka;B+<4yWw4lyb z1g(^rSwh8B<{jH*xQ+OXfih5{D(v#77fTpSZU6x4xx5JT)rNd)>t3>{4At#8RoHJF znKBO@yd@MHEp=%jO%ZP1)rPma)$<|cuisx^%%tWxOP+uJ4ZH6w!G|qA`^yg}{e7R# z79@+f93@%$#8=t*@^L>M353cDS5ME>bt*oG&d`6Wuo&9Be#sqq(B8MCkS6v1(UE`v zByiR2w$r7mWBUEdzYcs@g{a_GdNdF(Tw?Mc(+Oq9BsLdHId&iZiM!XIQ2+k1&Jc6K zt=eoe6jP)j+T?S)Xo7ZooSJHW!`K3MZ5@Lgc7CJiNZZ1sj_jZkEFiQ-=Pln?<1*aN zfOgG@_2?Sh1YkwZF&gxs@%N%4tmAV2x6;&Z&(fRV3-3k9lJkLL)cw4`&HmjHA=|5U zw#G{V)&%>AYpr_BEE6ZPYda3Wf8W_5U;`<$3EVlX)UJt&$ z)H-77gerOhgU%izJG$d}{0ECpU==6dF?!XKXfJry7k)}MktPPD0XQ(n;ad3%*Q`z7 zEzGr@v-5YHFFJwIpX~dzv;bUPz}{ z1pVcN$LQUtomSnRb3J$IT90BE-F*U|bN-d$5WZVyPui&?Wr2)94LZ3fovs@|ZoJmw zF4XXSU!VPv)w5~Zq2ae|yBaHg5R!FM!Vf*oeZz8hu8@a__+n(o^vUGiIvos(wqV~} z3ckk$CEn8=_mC`3TKLMae;4Hx{;zK+XT#PwH3sP0QTreBh`_+~F@3^jpQfgN z!_asj<)KpN)oHlOKgKnJ6AH;eW!P=ULoIp0v;BQk2k!LirPn&y=}Pn$^S)qsLfaiX zaM)dcdy+J zDUPZMYi}A@D&%i;R8HdWz}fb<3lz;!mz8(Pz6Ci=;l4F)t=T~Q;N!Oq*9!@qiSy4) z+*wyETRsdfw79*fn=%ieG=p{v!m`EC3h;O5##FI_oWWq`(MEw9J6_= z)=CTo_meDB$305b2#p7tB>uLx4(%4^~ldH;W-3wbtb&TiKCd@mmAh5PJzNp;d> zIR?3YGHQ1xv6!?_d|k2Th}65xT1&Fee7%lQQ2U6&zP+ZB2}Fs&!w7*&bvw`IsP>xY z)RhvL#1c_aNo_Wm9hvv4>nc3pmvv+8HHsZa#=5dsYj;W(lWeyqMHn_un~v<&mzd(f z$wGUY_JiV0*l7Pryvx*^nfpl_BiG4w2#?P_E@851BlJJvO-gq$cSv7J7-4XcYMY5B zUyQ_XSl#BJ9{dOG+y|$=3+}l52lcD|O1?AR67a|zu?UByE){CFfGtUQfX`B>L!{9? zcS}NG_`yNBQ)w^%3Rm4KDQ4N>tu__K=(H z9sKoUm9y*fH6EIZmbB}4Lub#aIyYs958C(=@=t_Ucpm%AI@6R`xRnbjjdiA7|1D-P z|8{&4EOHkK_{)PcOVhYf`o$CLgUUlqdh}kSbcgJHrib9*7qHJpjF?r``<$Vprh$R@ zV9v1<>|>Fo;@|vUO>3W|Da4+!6FvXG%d&q0h<*Ylm%wm!I4=IdW9xU$LU8|Wn*FzT zh8GISMiF{U@RaZV;WkiWftw~&?%S`GqAdbXfnqYJN{fGkwr5g{<~sxrLMWKTJ`Q50 zQzS?qmnd6v$T{S>1soZ6Q0R@)H7|cW{~`cI?g%zCx`kU9$yoA{+01f=-4S>;vXp8f1@r_lGc& zuZTQdS#(rBOcuR-tJJvo+4nB=p3feWNy&!+I57Lve@B^n6JHy-Zc*q~l$b`L2<9%h z{1@ssse(@M<83arBDEmft+8&Y-qU0FHkA+W=uJF9b4P~{%y%uGgxEOKE(RPZKEGK_ z03*24N2;w;pZ>`e^TEg}*=v4Btfz+E0+u?G=+CZPzcRNNd-fw)gxtn>d8u zY)*hp9@Cc?f(>3i5eE0jnqYLtvSwaMTGo?wtWdP#cLA)&!$3jyEI7GUJhXm026Olf z#eaN}ldg93dVjNy6utnn5D}hY6wpR+^53kjvO1RY80B8U47%moST`;AE|I94If|8t zkxb08s>OglJorMxQannd^G?A_<0>TtTO~gCm`dfGr-k>n@-C8qG4qfvC@K^V)+DGu zPQ(q~o+y6sJfLBj7uPL(J(UNVhX@Tk*=y7tH8}D^R_<1IEm4Pn+lGKov7Y|7^B4Lj zDkQVWW6?G4&FQoMOe{1B|91=~KQ_Vut?B=Mz($0`U2EIbaE40kl2Q8~auRm%pyhi1 z+aA?qz}!(u>R7RgdGqDKP)I7u>BGlZVoOJS(df`}o!tT(9w4Z9`m%Dcc408ONhWQ` zz#~YRMMoG1GQ%gRx`8jANFX@8!e%4Qmq-_YKchX$`Cie|;oJC(UhP&#!*TGk;eE3vaDkSmPMSu`IYx2$)?ni1(;IKbv!9?gbufipE+TN@WLPRw&M6b zi&107@oT`5`4KpuIPSrDOL8~7bj5=AJ;$HFx$U-LE=oPsckW^j95rZf9>|0|-;1(6 zB*$5{JCge_`fG9_q)JYUllEXbA1EMz%x+q~SPIsNJ?OlTM`1Js)Sn2f+J>I9&KwUN zso$@>e#}V_LW3h`RFd_7vKwaX%zI-)?pv>gAJ2!oz446MasIaj!Nr)@uj4EaxSH)=vL zY@UXc#nfN7ah)YI?i$;x)g858`>2U@u;O<_pu{3 zo|wDn;}FuSr@!^fsKp;5N`!Z67f=g+(C;$Ie=8Gr*!deApNg-_T*IAza$#VYO7z4` z^Ci#m4v#zcmldf4e(;73*5bGmH53xz1tzITN8ygtY~)gqh`&-K;YSealTFSBzFkJ( z@$Yis)SE2*3B`*&mzTbwkR~&1LwcTyuh^tV0Vj>I1FXOE`YfxjQQ2#q^~mBdNaYi_ zy+T%6A}YRkSd!;`E%N>EOhpn$5P%&3b%O{VMzMt+V=#WzvVg(9DhlRo)Q}wMBg^z@ zE2URBm6qIEx)H2?aG)aIVJIJsIgQ!c48dp-J3(y7qghO6-1xtpaf~UbQ6bjV#L36H zJ@Gb(F@`fnWB5DvnKe8?O^EUn8y4knvP_ybxbxL^-+nziiHI#9wb$SL=XJ}EsBaR3 z4sj=DBQ5AOs8oyf#mT*}e3>6HI)b|rnTPC2krbHqhPeA0Hzf+XPH18}BGm;{sA?3} zS}sEY_HC{-|8QKyI1L%4aPp&w_g~RM=3u*x1u zq4e<~=Bb1gt%9xL5{=E_7@zDAf?;deJPpX^O_k#pK zdGf<`?0-xqmjFAU!$TJn+hPCo31-U$Igs{&hu0|N4+ompG5PV2i=$GL84jH>>lkTZ zvZqI?vV(uz+Q`6K^G9g46@g=Gi{bo~?U9Zi*AAv!mf=5fe5Eqo8kgOT#N(7lk=8Y4 zFG=6GM4eWNyRKK~gTLz^OV%WpWhCP^OF;sMp+|H#w(aYxna_m^WQm$MSaD|4RhZm! zO&^grHmWJUdEnZoV^iOURz6`F0^ig>WB&1UVszt!Z?i|f%*aD%k9eofT!LVIurp}9 z^4(swKPNsd*P?f^CyFqXJLNZ+rqX0AC}BNP^mx5uJgIv zazx8Di$DItI+P2v1~Ef@mG^re9^ewN%*>DwiIK3EAlW^YcPuPS}_n5y_veOJa-)Wtsd zRtL-Un>Ep<_6Xhr0omO7oR1>8PkCD}zPv~-$6F@`Yrbyag@!Kct<$v@{-iJvue*kB zI~1U>7dy*Ipnbzs3p=vk`90_mtU9`4ol*QZf@~rgFwd2_?I1!~I(H2}bW_v~Lf6O$o6{Sd@vFwmv@XPtlmsu?16NktvQ7MEPs>=Q$VY zX+%ZhJzkowuY%8(6oXB$!zuNYxh;1epuvTUhQF=*vq&!GV_qQ0VqQ4KoH_M>MbE zB=r#Yf1aN8|Mm1n)(%}f075W!M2og~s804TQv5TR+K?j#6A=Td&mEG#0d_kR~S5vBlS zh()kLIqmsQ-4#9tIvC2C^U;=B@y$7!yvW2pdtUpOu+CJ`A!m~GD6}<2g=z0&W__}u zy|O40pif1?;Wk)EuV|-u8NT*cctU+*PEBe}WnG8DS^C|%!r32+dE=`@)-^#vgT?Ld zU#N0D@ERroN9CD<-GRmE#73WOW1&gm-EJ`Nr987I7H%Fow@VIxH*a>f{){UUg8x*r zOqiEv4dyo=0@iR_wud1i`Ad!at?Odsf(TSOSnzAfKHSbw|>a#RGe{&srH92Bc>BmR>OX-7R}bqYq_73SdBf z@t5a%InTHdz^wffEBL0E zhp!U^9_Q+h4(2avmnQg^0;DuCUmI|iyBuZ=Im~Ks#Zy?dGH+ytN#Eqhl6rmiU=PiV zd)?=vis1KyR1iH0go6!y-xq(*R_^=&@BThwy8ugQS0qekhkS8R7h`xZd!>+lSt86$ z@>Y|&#X$BA-9LHoAJmxw}`4*?oVI}&?kTC ztvoANPI58c^>z+hZaZ3lfXLL2%N5U+X>`waFfE3_-%<#!R_qWimVt*V;D-a64q+!vFRlZ z`$&mWhOUxFt>A&x{{qmn7Ft+vN{DrY(4Ow&!JCk*ZTzT9M}pPKbgCV66ksq~K%DET zR5XqO`E!v;t@~O=*8!9|3Fyo4z7HOgDlC+?<*S zw6I$(&?=ktRdo!t{Rxf!A`(tNLF>EKNyL*_wOo+z+%e_lv zpej;-``t%32@q|##9{eIBC4-^s*e7tWG9?V5t|=No>@|9lj{8)6~qjf8|AHZ{aOGI z>oP&5LR;JH^Ofb;#cYmNh%J!^?OMx(e`pY@uiaqG%`3M2(6dwt5uecOY8#&Uf>i8{ z06znqvSH5727Vh*sZu9KSyYoBx-k95A>~dQnd-9XUEh|r3 zy=W`^X^4GLh9)A=mR=<{G3vLw;4J=CldTu_Q{QIPFT>rQ5kGh(gDpZTyT~7&fKBtA z@g9>|3mN)W39TwT69v&Q)NpYgVlF}YY4a&emy3y?GEx?ChcjVtk*yp!mUsplrz2OK zB8zZu1kJ4!XLLkz*34s!%5SDa{oU%Uiu`K*9L;TScMGe=2nRB?kc7d`Q$^eu(Ii#- zQFJ+RC`#1zMoUQmX;_)?^Bx_0XV#4HXt~Y6X1qfMLZ%#w3D$1q&ZY>tCe8MC)0kA{F;VAjhtevagv+jJswYQg>Zn+%noss9YZ2*e(+Th1Nn@8f-tbx;MK9g;e(a}ts?Q`Vz7n<+6(ElII=ezX2_ z8V#(C@j*jTaM=RF;M^t-T}ZJU6RpqhqFHjHZ!=-CYgLEctQ#zAh{m^vR)n;kyowe7 zd;nAzd(pZHWJ1E%akXKO^d|uGazi6dw+AE5E(NA2PURKRRRHEsq(|l1f{Fl-kPiS7 znTdQd5~+#&%ojr`8nsh~XAKLSoo^OocYtqu0LG3tiH)>7E%ms;*(ej+4Refmq9Km2 z4)B+_HH_=j63od#)}JT)hTT)0EmVrip~PbqV@&Pif$ z=@#l(AcE2bAPS+QJlJ0CAtVu2eeAxXrbg5)qU;yeqFtVd+g4)6M{z=pwKA^qu-y9V z+v%|Mpih&Jw*5XYZqNATMjC8SbYS6Nuu#k4lGJ`YthI07c4M(Jmv}CJ?v75 zwXJrYwASj$KnmqjCi-nObk1@!D{|MkbP7m-B{#buDXu4x&Q2F437B5OX{snZMw6EK zGW)tL`r-*6W*MlVzT>Coz-V4w?tEMI{w z7rV%kBp~-_c8z7Fg*`g=X(jp@MS0q_JTHZVQ7}K?Cf1^y0XOgNV~}P_n3*ooKfDFq zC)nK5ZwYt1vDQRg5e6kVgD^IZ(Oht;@CQpp;EPaF%z2XTE&QTP^|rs;++p6ajlZAJ zvF}jvXAjx|?=K z=Du`cvWUTV$5B&*iU3JCJ1sHeAl>nfUP&ZL9w+F!fltd5j^kFW>^_9ne4U_bg$!g^Ysi2-phe<1gfi~AZO z@QQV|(|U#`XwHhj^LOwwZ0zjZ7juEHo`z|>DM@=;N$1TPshpL?x(8+MxMKYzA&~Y6P6p9j#?%uSul?~iPQdNE^#OD;rT! z8K}Dkad{AqbmdvP$^H?Z-zEi>AqKm!g=ZY>Uyv!X0wag)uKozPhcpy6upjh?B>0qo zC2JxIVribtl8OdCVdHsqsHnRaWz&NVVF6waBrJL$n1dhisx(Bb*MDtbMstNY#!XG| zL~Dll>O@Evxky~X&YckaoP@7HSPjPW;7p(!t)o<~dUG(iIkl<)EK^miGlijjKz+2z~WnYKuQn0%M!S*iJ)!_rzfuoiGrhDaRK>UPwn^ zk(nK$vXnD<-*W!w)xK90%6nuAf-)lU3*=MKi3QDyLLRgp+y;YxH0nfpeaGGN3VfX( z3Z;A^T}729?E*W4ng7NS3~=KcNmFBmtbpv1LqD;^YINV@-PExaMX=yGV7ASkkJFH} zMwI`2a!aJ5mxo&?HX8Rz(#q%_eWHfyi)}NdFCL@{h)ORd(RJHXVB4$a(VXBZdh*~n zIWbLun~HGv>%K6ZN?bQq<{`IMUc2KGit|!8h=@Oqq zklF0-=Rf!E0LRrD$!YXj7L^OpSEWqHB{5AZxW{|gwFT$XxVg*MWemi)nppMN)1E)D z#C6Gh=d$KtC~^oUF+SODaT*}k7 zKW$>4ej@QO~2t&b89E_1a?@Pm9P9Rc%UvrT2$t>n)jXOMQsvCWd}D|JH_K@_@i^rtq8k^8KI;~ znrXIi%cHVAwvo&}VLU=f25q@mBmqQEbm?W?kpen4Dz}LEyb`xd;|WR2G$uJ>)7GSN zNH$~5!87`Nll=#Iu5fnSH_eDQE|HKap%%o{m!6wx54u$T&Vut(dHff=7C)aDbZJ1b zgku6eFGssI*ZlG+<(e`HN}=(dV5u*%!eYgV25D5mp?HeSpIsDNnwGmS^~J3Tq4_;u zABH3lBtEm;y&RXc!uI$<$@SMiPH~Q|GLo*)L9=H(KOJ|6edXmmCoCaK1PU>uoN>nQ zu{QsKzoW9*`edj#*#V3<5zY%&OmU;2uHtmBrJUpOQX1|p_b_S|ahlIhV+1=i&W2)V zyCqrBBU)P=HF~HthzPP$MJf^9)W7?Isa6c!giFy#-+U^GqBNag7!}VdtA}l9r`QAz zTbj3cbmUL!BWtG@WhgZ9NlWhP%LXGZn0H*xK$v7YK8fsbSc2V)%c_srjmjcrV^7~M zC4QEBOu-DqnJ}<)#`^{yr!;EZ^&?RSdoWI#fQdfhyR($vKOOhOTXqq7!FJobAKC=QXk&=Vtq*DnGfE>95*Ycyg=lpd?P=Q+tqAC` z%3iBB>H=`KRUWKIV*^vRZN?G9D>Xcl)wUdVcLr!yiNB{?N{ExIx5oK zEnl!sr*jftmIp0Ewnk!MIiiHbn3Z^DU`s`_-kf<~@{I>9~qm2pyV!N?9ce4rh-LD_4*oG_45S zQZ^D3@6*naZU&}I7hB4`SID0>%y!=n2l3 zG;7v1FSw;j5-zXZY%i_*Djq0YwNCVYzhzbZQ|G~fuV}j)msTu8kJDV9dI4LMz{-i_ z@CKCo4-XKk;5Bx~Llv|#z!N3ZP89G%y7&{sbSA<;?h@a$Kz9fnXYkA~FVchT7G5DR z9Lnxcx7Y>qDr5Y>Heq*W!?%1WSL?^Y{*#8HR$Nsp(uKg{omIfNA}E)@jo1-)g3TdG zH%yxz(i7uN0;=BDShqzC4dx#cDbbGSI2vq5#*&uV*kukqDOZ=C){tKr8~Bh-*9pNU zJHhh6Qn?&?sZ75^xPr$TB99i$SUm%tpSfR$a3Fx zFfM6GE3X%V_&?|h^^509WgbSE$ZXrr+lfiDi{UrR>vNt+_37&SA_voEDxBHY)9xjM zc_LH_6jV?WZ=EjZfy!F|>6z~UX)8;ED%NaPzWz9^SmyQ$D#F^*Cgqglg4FD^0`@6U z#(O3*HS%#*)i#IkE`Svg<4NT|Smv{`q8}yUXNr%f{oGeyZWyif#*h=C50i=1R(_pj z4=H08aBRL#(-W$zyM$fI{9-`-83;%#n0hbm5h4FwuYZk7V7%0&HlVv?XOg0r)n~r z-C-CUHb${fL;5Qu%~z6YrRO8fr)+my%*0Jt4y)-iERv=?dLZR9CQBpgWjaJ25iH}` zV@fbijZ+z9B@^*K^JfcjibP8%g{YpUZ#4~IBMooJ&(MAh_b)mTlae)btxBsPxG+_m za)H!m7EBe-ghM-`NYFF!M}dU)7q89tTn;*13ij7}tG#TBb?t`&dAt;fxhtc5yUGU} zwH&*v>SDu|kt|0mqfhoj^!58&`>dNdK>Wyf;-whjl~P*kq7Yx^>Q$VMOsz52_Y_7q zx4w%^GWR$_DoYg0GORL39C9$lxi|uw$XEQ|+U^J@W7}hcr;fjwVDPhV@y{a+sU#6< zT8m75>h2BNvpfd0{6H6@D9-9qCYIOqSZ&U13?t?5+E*jbi*Zf;MR0w$>s$H{GT-Ek zf*KnakBELEZN}PRLbV@vFZ;h}YWem^&1Wu$ABALPElY|W`V8@#qJQ-upW z?4WWwfAWG_=KYg|&L3sqqQF02ZKAAI2u%)uwzcg@-13whG<1(DX*91;z*$_I=#k;5 z;72-qA@pypaVvYg`Ft`9&F=lSa%`=3-!jm&d;d67s_qWw5$lOFKX7bga_y5?3R2+$ z?bU#h0S9*=B)GhZ5s8ryjzaM}xyjcJr3+AJ`Asb-9WbIO z+;d8OTJUVwmx)nUqGW?7YOIgYxDm*EWBPDjO|eLpc&6Q(w|**@sX$2P*>(Wyz=Gci z9$Y|3=bA>5=yzH|1pYgN`!KA*C_vz6EdWP7nY>k+b-Lsq+~LR{WflYDJum>JnRmNO zl+RX;7_#A)U`ox?uL)TBXx8ip2+7Bz4`j0q2R7XEd&uUMKri1M$$QIh&}6IaRQF*~ zOFJxE!ZYq&$l)>da-4D0z$_~p6AJUL$ zn9ElfCi2ACE(TxhCD&4B+sANY{nA>&W*}A>$GP3UyR@FgB z_U%}K^iWAacO+MAn4l#!?C@TY5L+C|EbN{g43RoGd^~7N0gA1(aBJqT%1`i9q-i~3 z9_N>Vz!UHd)a4l63=c*2-Me-%N~stoi0}?JK@=J5KOQFMM2+g<;PP<_=-BGb4MuS( zqza2P?pXhUX5bcPyTjneRE#1wH>UYrCX5kg;J`@Ox%K#A+SQ^tTA3;=?K7Ax`1#sH z$+BQLfaiHt;?)EGC@UvUd=l}*ZVHtYT{6ivZR&QPd8E=?Tn-!GQ>(FG*5>=716jw; zUwlP8c=T?M{6>}|T*6J0>`&Ub(~>~z;UtUJ-EotdyY8s0UAPjjuj9IGM;j+wyLei% zz`d~SeYC>pkh;@%^`TcZcvS6*aXci9X*1N7PXu&eVPpr(YxXDjh%Ng=b(CXi`70kn ziZx#Zm0L%&2Bpza*hf4NbcS`Qx;G0I7=P4_4^dzIsf3eUxqQCLR@G3$9mz8F6Q|L} zC+S^^v#vYsS2dR1KwHM$JZ=0WLCaz1iAUSPjGZ;e_}e-)_EmK}0p%(jEIa78 z^qJqyOAU+uZu}O319VCf3(i6CX-Ep8zBeJ+dGzt9;~!{!PMyPO+ zQ@XdNZ$ksv1rmovjP6CU<*Ea1MS+KJ2)}-x=IaiVb+N^WhT4|UegK*9$R$;jf)czcGkni?urKy=FQe=Yc?H816dnAT9fwYej zs+CM|F3Zrvig(OE5*!%`JX-=3ZxnF@9{EqnG}7F@WxhbDEY-HxPnp9o;N!}1@v7km zUDcYFk%mjPQn8Gh`(Hnl=`wyN5*OIy%hV_xmUzHFDV&h8;%pvl0lgB8KIqq};6hI) zw^clTgxxslP0215Z-l?k=d7kn`z=ZKk78`Fv|$vq8?%45Co?zXov{lp)OJI)n)k#) z!vUlIL@F}fw^as@UgaC~zq#Kv^9`X>K8Ci&;602ce%}MCjati;f4KQricemPHj!0V zj+PD^S4&PzHId_GPsj)OCS)735lixFe&NETqogvYl6`e9T$w(&J$P3_&rHwE%xpni zMn+s%Ifp~5QBgXlSbPgjMWzJBJMj2@0j*SJ6(`?~U6_7i z+>)0jD3aQCapOiLu-{$v(LpLC;4~^P_v%5&oYoWptLiTbZ9>mS{g5YROX|fIoiwKz zb%`EYo$mz4SuJ3ZO0oseM-Ncm=rz{PJSwr1H}D;afvFLeXSp2#2S7a2 zJIreNE@{tf4djhYu-F0T+I?0V)pE#XsWBpO5-n-6uLo}NO5%z7uJL(?$vmeiu=b8Q zAXrcQuHx)6yu$+Jk z@Y?-XNi0Keu0qD%KnWxrKkjnuq4n6=oaYNcByTxaSb(EumZX~WZ(CdwEq$}=z-LF zMDVO^QC%viN6*cP@OJC>H*8@^6`yn2TNLiN;A~758)y;Ntk9Wnxp66KY4uSob`W~T zv#;GKqgAf6&3MH~<(wJ_?0Uk}~Fd%$-dZ0@)IuoH7Z zpqTc`bhV<+=it-*R>V2mES5nKzNkOQ9*oo)hyaGd{ifDYTU4se}53q3lG1Y!fTYVrKuSMP+ze1E+d$M}Tu z^rBN$cA~9Z2=y}gJJ!kCXY4G-JN(l{nSjvxZEx(+G+8#kyO|n^($hV&o&#TB zC{|107B#{x_=Z{1@k8Ba%@2dn3hf4V_Yp3bV!}5)`Ofq6NW{X`9r5mW3!9E(vVlmR zJ_`K%6@h}lfoSjC%I4}P?u)xjh?CL8n;m2!3+l&DC)`%=cjUu9n!Wa|-@N6f92fEY z-XLobCT{OY9~-aPEl2;fzKH&j8V0>Q+6*at^$IGIW2*}k`o$*H?s10RC{H|?yMQjw z9jMMnr+h{~0!z@{#0X2XG;I=o?b|pe<{N;hqzE#?gw6lHcpli>`qqDRrw8QBUHYR;L;0nT^(Id zErki0sH462)1T%Fcdl74mzJjD0#Q?dOC*m4R;94eowVqilUz;g!)qV9PHKvmvYi(l zJ;8IWAG|YOnoB=Bm+x;Z5rVov6ptanT~3ID1&dy*WPXVd*QpXA_ep__kSh%k+vSEp zM(D*wGtK4k(63;bfHUmdb>WclPB3+6=cVCshd1d!cg^~11(@Hj!&xkROz5<7;LbtF@i^s;b&_hM%W_cXfj*8u=5a9eNN+<&&kf2F;aKEyHAo-xcb zFd(MRGU0{|4f7XcA4+YZ_e!>-PIxcWPJMdr`%t}EBLy1d^C`2hqO*i@aOiU-UR&@Q ztz~)odPr;MErDVAd>rD-h%0t{fcD1E11N~_G~D3Tytx_AP3`eIUhv2GMwrGx(-_pz z^^g$k4z4QS==y{oBL`rXAx?v9G?-oi26l|ZAB9x@cDBU@WAAoj_sSdQSl;aB-+yqO zH(tSPz6!=`vEIrE$-yvr50C7>l{aFlSAjVBa!{Df z?d%(QU*`oJ!&ZG_%lbj;PJw8mcH1lCLq)5vTl<|C=Hs0x>HA9>8u^^`P1EH=D#Z}N z^noUeX3y|L;FlX?^vbL8gjgnQwM8CWo6k^haG8erf_Lh~$qg&aZU6UA=-8qd`*YtC zz^$WRLH1N*AkOoE(Lh81FcDIEu<+?3XLlYOI;H+8@}Ps~n6Bhi;3Ku9J5YPb>B+U* zpkGJ6iZ5!<&tlE(u`$_c56?qPskkgNF4NKjr- zQ55l$zd_IElQvu9%b&~O3=AX(;6ft7EC+I>>`TqMKC^7wii!8Pt@qdhy5$tRi@{H- zTyI|>VEMM2*5|PUxfVeI+=t~^o`08$`&7r@Zw}*|`&Z0oI4(61qkhrxV>!*wG8&ec zDlUiR0|^>jg?tZdckFAg-b=QHKJ|*4*OINfQ0WJ-Jn{|s4wfN5?U~sL#VjMl-FVIm z!i!^;z+GrMdG##z>%Uq69F~#ip7v4(gBrOKTj~>^!`(u%r6H^$eQw$sC%xoy3{BcT z6p?X>K6jgX&A%5)zD{Q}I}@*Y(tXCa`mKM9iA~$e%gqod*8cPje^qzT7}k_a;_-z= z3h3zK{0nI0P-?6Y)Ysg}zA8lcHwb|H86&9XtUCY9kKa~IE}!K{5Y;Z8DWrz~6O z&Jty?&e_5;Tw*|dYMY@jdq`LY-?R3+I^ihfDp&!0&v-a7VTMpqQDyZF6p#@=ukPy- z4o;r1DD=`Aw*h|18ch){Cs-)y#ik5VF&9rZXJ#DD^<5+mkm;9VqP!wkSgrie|!=)N(hovJ!4Q;UFHi$ZIN4p12 zRWJ~CKO0vgF1nq^gSbB)e~3uRM`QJov5~4>?g=bwE_69f>Z1AX->yvcCjsDr*T%4u zy*=X(&=)7`Z84R2X06e&G0ng2LpoMI-5~4)GjNCWlcn2Q?RmzD2P|+cAP`*lUmqGn zG-Mt^*MDWDZjkSU@;rB*`@F96Jg@7% z5>+H}5kB`+;lOL6fd914Ql8Pl?v%yxcZ%8kt5;IM!9VR+NUBpE_jle!d5uCgTbJPB|QJDYs85?Q($~c3i z_q0gx*xwYlRpa}ucR2y_eh*#UUTuCa%SYt4@-*E?eC!t9{18A~`h| zaS0RmJHB93t@-;iS>T)H7U+>f1lKp)W|sZf8%2?>eGaT1^A25BFQY1SOtX|2f}!1B z=##m*^Eq0khcXuP_ElzeGN-0m#BWo!%Em(AlRon^B($Bon%tyyceteNrMf#u1b+H+^hVT?*zPLK3gT7 zl7k<KSGK**K?SHh>c=YY|5;;BC)Dx(7})YjJsnkzh$;nwxuYMB6wD zV+sZm+Mg%j`ZtJ|pNc`05+iPWhsts+BpK?z*4t)R-XpZIC2g!1h03l%IGGa2VLgo=!wgRs*f+X) zB&6oS!LH1wm9~BO!6)SUl)AxoVbXN@IrzZ&gNprC<@v@Jat8{Ppo!UCEF+-{FF6P* zYqu@8=Z3A;x<*Y*eDno7R{!C%%`|QUHF(Dz2DM8e&t^e%xbo(diREssbD^EnyeJ54 zo|X9K1k9Qt>&xeZ&38h(kc0kWhu-KUT^1)nwTYK-qwB?5d+o=UvCkExD8>!n7$R!& z_AXCk_+kp3827UqtJp}cnSajs%kPD?LU$No(|7qJPiCYEq~(I}{BF~aA=Qc6GMK9L zU-+O1ad*t4gFS`WMYvPdAGv8f8-j9TY?o1)dIawnSqT-pWwC_zmd&sI4sYANwsYgv zU3B|$-tUIU-OPaLzdzNG&c<$-jB_ImjsW1!?y{I9U(u zkRKdlxQE>z5J$b6sXdaYMgzz*FM&TiXQkN<4I`bmK;_w%eOwrwcE%Q$y6yP2(ab5- z81!292B>r^&@k**aP!R$;566~-oseyoN9}@O&U70MN^kR&5RD%!dJQzuVkU+YMZTN zZe^WcK9R8QFEEVy`I)sRtPAfKq4cjTnwkG?&UHrxdRH)M-ZI#){r0$iNJ3w2k>5mcvp%FeZ969#Md4YskiAGh0J|4tWCF6^&X7?AeXY1OU`SZ4 z9lG!t9B~b6y@g^P_;gDo>j(oyA4rJO?Ver*?Jss;qkEO^Nf+)?JaZLj>u{cOT=$*U zlA+eT#wThNx--@+MkREMGFFUcO4E&d&(XC!((Zlxi>bLzWE?z5{cL@*(K40`vbL?c>^LB?uLe9Vnc1kTQIvorVkU8Q~2 zSlstYaBhhBBw7CZdV5jQ#kuPa;so;|8Y|ma1!D_-o`;C4ykZQtd;z)`(AlNzV7X~> zLb6UpBU&E>_3`4i`XzALEb}lTFb%BzIOr`gpqUZZTv7~afnNGkZ?zN{rXTB;-e7+f z8hB8SwtRTUd$znJpJSDe3Y)IG5>Rtk%c$Y`ZY5&q)c`$M7qukHPCgep`vH&EUCyM> zL}qdSxiZD5xu5;Zee!Z|(kr|zBR;@zQ!1hq|C)ncCl6AvJ4$xIM6+LFnscx{to}sd zI<0LCiiA(OheYjp*m1O#?rEWa6IVPPZTgbNa|MU^zvXHw(=*p4Y57fcupXmKe z2xEn%JZDZ#(5-5WffXI=V{~(wnK(Fvu*zxFOKEj%)?en8h1|8OVpKJwzsbWFI z0LCwydr~u&NG%s}Utmx@!6(+^WTIQ^iQId}K zkDJwvD`Uo6YmxI?UNz=ljrcI_t;REcfREuGmH12#`6G+g@g>va`5@hn=N3ZV?OlDr zvNrdzYk|{+ZHE2h=yEj@-_z#4zvz#Eu@nVX+WHETeTD1tJQI z{2unkrRBK22D?|Cwg%VWM%)*^kVB&!ARC5TvyD@$9%@ z9AUjT80?yKYSkVtv(Ku1d~ts!4#_-c)=cknil#^3O^~BAPc4CSKhF%gYJfVx6gyl= zgL){2Hd>%W0_xe*BguImNiz1tmo-PZIOwFZln%JvgKz(3JM46|llr>KqDa{X4^sBd z$*@|h*uFJfBw9%#*X_! zpjs^0bMqdh!Sl%#(z^snlk9O%rdepy3ck;xUspiWliO!~dOC7vFZ5hf@Z^2R3SwZ? z>irqIwnlGm$CqYY3srBLBbhl|XwmHBwKuScus04h2mFqdkrFD`PCD^a?z|$8*eaE9 zPS~D_YlBn^kxr|!@9xBmmWI+3wjSlcpS@fpT{K@1GUTJ>JSQ9xD+v&6|D6>Y7)gkd z^iVSY;ZiLc_doE_6#G9J(|Xi@sTE~Wb04nFVEBQSXo^kIn{J9%5yu_k*ZF=%%bsOvrbo&O&U{b1p9;Bb{Nk$>|!=W61iKgUEq z>Cfv@2Gq>K?doROYEaeOsk%MNBzIYDvZlNW0udqpV(M9!4`!=&=a@{M{0btvty9=Z zxyTL{k1(qI!e@LfpOu)!?h5$!-6;*K!NGc?{=JW9dzr(#tVBG0gq7Q5S^4AU518fR z2v=i%`do6UJ^GoNJzGw@?(XTjVlWSu{kQ9nhL;_ z3s7`-fFX6q`#16iX%l9BuwE0xs$b>ti6in1KF=X<)+XdL^phDa$05ytq8`H0hm8R& zilfM=iAsmdH~|FtO0Z}0R%CxBl5xChlI3*aD{GXioDnL9uNaUU){}^dUeAj;d|qG9 zVEf&R+3v6$=bbl`QU+3OXNSROhaj!KBG@5k5ms2bpODW#rC4f~le2D2kTELgg^fVK z)-fRRNVS7gQv&hNlxV+XU9T5HyQ^++2K(Qb{1k2s6*lCC(UDH(XSu#vw9@-vvhJZ@ z6C}M*@FQmI+TRk+EAT%t)kVVo-?bZ7a z^X1YrC^IcKK{EpbCmg4q*($6VtBEj)A&7=;*I3&|t$$;1G&UNaTy%pw@y(o@=>1la zT=k0q2oosXpo|p9I;iDdUQRh-yaC3UX5~EsI33KcpwHg2TAirOIPf>|IsjV0R|~?& zjz3<}3|6UrNq7~>#)hKTFW8IXc&^A~Vo!lA2$R!eN1n*$$OiH8y2SXb4IXKY42DKR@*X^V!`1U$x z?DiRorNL9)S7!d15_avO3q{Ly&@_K*UL4;z;T@dzO>Ffq9su3NT8%@1J|H3z7)>z^ z>qLJ^_??7*RXjh~)5=PELhcKh`~cA9j&M|}Hj~oW)6}pW=w>x(zevg>7C(OW;g0}N z;iK>E(5Z^+kTz0?BXD*I2wQf|Z#{m&{ovk1rg5Y+{?;oDQ-DxJCaO%kpmv47Cev`< z*Mzv^)-(3aj7NQer+2!a5~6_Jq{&zu8%k;O217qGhzu!V!SX z5aTkZ;7h)>ut(BnReB8l=?_SuoD==WOu@#mFdXG5_;*^ehRN8M`$Gg=~_@Tfb!K83Uuuk36Qw?NK@_gm+OAU9ho zJ65*xW4iQ#VTzw(ZB*PjS|Ub>=-Xp)bQOnI=S4C@zUc|Y8lJ9;K63bWnt--`^?TkJ zs40q5BnM{Mz8qsBFJ&*m=SaM3 zs(Vf_4_w(71jJIO9&km(&)UwCeQlHdy1 zaILIyF7(Q3D=L`~_G{rx2RY$=(cVAxSJ=Xv6gMkKplTn~;~&Fz7?gXVT!9xsA|@oK zMfhp01s&evx^PT2={Wa78}Xu>yNq#WXs-}N9J$ulmie%M!|TCR&B7hE6>l@t|Dot6 zX}lcME)8<7{s%c4|3MC{0ItsS|DsL59&HR+kGdbc54Vs79d*70c%NmU<>Fo zGrrZtunLnT@z<%PIHtVv*jyK{do9o-gTDq5BQ}#7f=Wg~yq2E-B5Q>;R!j&2#Ix#z z%QNg0qE#lNVPYz1>5OlMi?shE0-bq=DZo_it(a^fiGhcVi#gYk^Lo4IkoI+#0}3=l z_hk89uBS;F`Ss-M{!~BZ@Y&CAjSPI*3s_u(YMpD@RTztG#sNB9Tk2GTrtaM+~Hf)kvTSq6NEP*3Pj)5|TG2dq*2!HXdRbagw^%-<{?2dS`XDqjllB5%Ut z*4lNwpVprbeh!;-`d0ulZam3mr+g0dlZG`@KL6;ic$>HXeVZ=FMrZd$Y{{JMmSPGJEvCCsXk%bl;S)VVIUF8^r0e*1yzUMofe?7Y6=-vYC7w^xvWhQM_cH#KtuR!Xm2WOw-!AmGp1Ne;vH>K zTy%1dbebL7gBluQ(#yx_6hl@4js-W_>G+yx8&`{CS@Uw3N$Y`X3~=iGC7%r`f!Esm zaog{|Z~WuOF;g!#JL$X5$kSvb(gg0+61Uc|64vAP928FA!PeT$zkcW))lrSnKueO# zBU~e5j_AoHmWx@( z4|sh3T%U9&&BuUrz^d+&)u!DdXn$i>=D8tzqV&Yo$sg|asB5Q4_72%e?JGZ zw(m-nt};uc52lN!|X)&7smAflWVkR{i29iblv zXCTj=@uI>@`x7w=fl?5ws*i-Nl%peE#T@yYmM6RIx(MjFHF_;j68O2^ojx?W1b*aT_zC0K{`c27+a51e3iVTRxEb6d6OO; z!qO(n$L&^H@C@VQ-jzPUP60Oe=M*QTg9o&%rZnJHD?8>KE;+za0QoEw_r_r{j-4TFR*F*mk}~=V zbBXjE@6D*{^_1mbZNaZPzig-cY?Z3>q`Qds?$h_DYMwytOh=@zH<`~sP|VeoRRX59 zXuTa>hpgb-p02p1wB7-X-1&S@xWDlu$$RE&O4=TQrw2ebkq}|DFZ5t`l*6M^Vf7s^ zYqowM%}UgLHY`6OQ1kv50_v{S-fRy2ElW!kcNupH57(m2R$X%bj3RR&{*&(4k&r#h zg|D&xkt|gLx1vyXsM9pO2z{Mftw;+jvrwuYI~K^UgdBAt+b6v4Z9`Bn*aw)NPlMwFd;o-heC%8}Nl z%3@vnzcBbO!>_x{C#TmXToQSwAEQGmpVuj~sknq)pNMOZ8vt_$G?3@%E4GkJGeAL;%n|QJMDceD7$j-I=X_d5a&#Cfj*!nn0 z)2;_L=R>0?Ho61bV)~?>&p16BC>D=aLu|iAuSQD&L*|5T`Dva4y~4Sho;Bx8fN51Q zyX~dYr~Lu29>hriCgymD<=Q)XmCT?ZDSd)S?F%N}g-jClO`LSoSyp2*a@QDUatlPn zsaFta{i_tQ0z{hg0Z7(E$^Hj5ev7NLr>JECqQ27qB?lsNDg4V|N2_*XRTxbRwFC2^bJw#GPHZ!G5+ORK|r zHOdpl`T2zTZ<2K`O-cV5)S&?4bhDF(e&@)O5#Z{?pE#xB0p#Q&Rvma>YHH*laGD|K z)*17H3@emATh;x%$8Z&EwMDin`q*=i=@>iA{Iq<<0?owvsBh2Du+5WWQbWTYb(ga6 z#`)^W@!e*$cL~H7{ycUjs)Gg&*O3mEUKccv7kn`PkyP3zoDA(jF*7&jsvidleu++G5z7o;Fz=|nk-pQWYEJ&zoa8?^^Gwm z3SA|e)#PlS-=DQPkn3p-Q#;tb z%-l-95?9s*S`%x1!YSL*-mB?3me~Sh@PcwB@{!_D#W#>nXI>g0gSSLhZ#PQ2HWKbV zeHW9h`jUN=@aOQ)cJvwmMkDF1QcN18GJVrSFHNU~8%P1_%-FV#P z-`9b7JlD`1k7CGl;0XP7ZfL%oW}T~v+wRe9443H}Y`xQE&lyn~)-T(e?LR;PtO7pw zC9bXV7tif#Wk;br5_|q;-x9*5xIW{)Glhz|oAhJRjT$1L?o{{(n3Tv%+1#0r1UtW5 zA+LiR7-k=!lxN}Jw$Ir^zVV)}8jsO&g)P7wOI3OIWK4~+Y^i8lG4 z$)T0`y;xusC>FDw@k{xlg6bFlRX6;DW3L|g_ct`65n8sy(xL$gw@c~(QjBOuc<_nV zC+_Tq&FhCZzc3z28Idce@4@89=Y0n<8^!wO!^}yWllsK45*YJsw}FvI9nVXC`7afj z4Gv2UzM=jZxfwOTlS#scWa5k!ee?@{rD>@z?b)La`(Dv*5?eY_pto}Mcx6Il?z9p7 zc-u@gVC&s$t8^4+z~I~oy?~|`v-*q3_?UWc4?6PX#Bfr33n!D-mWGg9^kJ|1Zip#- zA7!F_rRntddgRF_@}gZx*?!no>ye-3x#&&Rk4?9R09$Jbt}_j<6bWGl%?;Hf#?S@s z2)qo|uf&^y3!zR+X=Nr405jefu#J#((m!HfI7TbXIBcM~fD7xORXjJsWz_050U*@= zmE=med@pZBLu`3xynMA_BBJ=u6{*1PdUrrCva0fz13}zMzse?I7|m~+$p|F}U3l6M zz_-B2_-oK)$8|1C-JSRJ=2~C);jnD)ktx)R6}~OlJFx-m2~lc=U6BsQa-<>cn|Rfi zCMTkdt?pCeMUTa>C(!q^rKMVoq0=ByhAWL&dSMXps$=vPkioL@+73Y3NgO-9c~9h_ zpw=avxE8z(*B45ofhFnG6!Pf5nVS-kCNZtLrk}+LxyFd5ki7)Y1+_g%N~MDwInd9zooUA%j=k!Bl9*7TqxMRMO!;G0pntD4a=tF&)q)w> z{=`_Zoi1Qcn>WU%qfUn#aCN=@THti#?u^-Cp$TX2RylEuOrK=jYMk+LD~l$V>2%mN zr=T|~Bq;(8V)*SL@0b}g`>xJ7+hWZ$@)H_a^dl3!^xKPx3vm0q;GA@j;YO-w<)#W9 zbxFLDF6<=zpnX;9pwu|YYhlo?hVAJ$>@74Gr<7^mjQslEF0`MpU25O@*rEQuz2n{- zl-{<{lYQp+*cP2&%bb0S2h^Abq^I;;WsDrd--|9YhtQEn6#L*s$`rHONyJ z)r6N=Ah`|d8;lmotdir z50q%ij@>Z_+2B3RCkJ_}ei{w)|Ie~8{humCbJWG~(O%GUARm$c3y>uK1EdyKlXpbZ zyl7a;+d9|M1NAIr>)m*kU2G{<;p$uqS3j4YIza&I?a+StU%?3@S~Q4EQ%vhF?f>;e zGEAUkpmzu+BWbbbKj2TH*yEj9j+T(PcjzJR5A8tQ?7&?Gm(n9C^6isPEz2WqPFg*g z6=Q<`I^NhlN*bM?d^iV$ab3A8ou+?wtnU>`sUnQJ*Ggm^rQO*FK{EhVVMIJLIhxWD zem0;tH~|q-*HZg10+Y7dYcAPkT&^(HW>)o-fPR5)2eSsal3U2qhzJ(qK{H3B+J=>@ zn@Ji6f)&r@Z>Fc*7neb!B?i@M4XSrnOY-$m6ef>pv)xDDvx<1TFy|ZlgX+e+zo^TH zak|_sjU3gr9gONeGdJ`5GZfh$5JCj1i#v+cPD=Rmfo-SC3z4mR^x7bUAeA+R9FCUP zl-=+_&{)qR`aM)+Eo{_rj?sW#5rK>6wM3IApZQNj%P~EX5U#P^b7VyuINZ zN!tqpP#yl;&y2EtaY38R|E^)jrPR51Nn>Fh%=xil(dPndSqzL3cJ^s>;=8^m#=D~# z@U+gucL7e3+llZ|^F#|1w)bo(-Ml09ip~4?dld z>hd!c;5wJwmzgWpdROb zLG!Bd(<^?K#^+UlER#%NBBWYZFQ$%$gIfy`Cw-(QJ%mqhwi@{K3SMqo3lCjGkr2Ns zA2hRHUvIpw*s&mtK~cnD(5+D(+-7}tq+6cHVVA3xE^Wc5XiHCkpw0U z)U0$Cv@3J92JL)E5y<$)%(W(@dE+f^I_Kj!i2 zzt+123Oi+^d2)!s!Gs9FUN-w&#;LY|5aVnlXHPPMU0iatKRu>0rQ)7545Ew{6sZt+|3Gbtd z9>Z2wGwEy_lZm`~eiRd}>x35fjnFi1_%SCfV3neP;nz&%1Kg$85r2`M1|dT!t5K+3 zV}|41A;zbVQ3H^=4BWaD@eG@dzoN2^2+>~2%hO~F_CMH)yyx^O=48ui*f^t%2{vDI zGcs_?f;I0_O7QL(!3~4|e4o;OlJ`ecwCzOsRKw5c3s~vNfWCQNUKPm$eT}H_;YQIO zbNh<}_Jy1NE+hqeSwio@Pw8&4w{WwR&*H*lNwX=?H1C8dn8L)YfM@%WJ3Y^AuL~^) zQ%E;4ZQ9pi{ydmLZ4DlE*r?Kimlid%QvN{jrghhmwOe2KNO8x?Lss%w;#B>8edIIi z4MaiGAcKAF+xYcSh)Y{02D;={8O*jC4QxMunoG1dM7e)|d1SNS=rv6*Trt5XhmWmF zmiRS@p|1gjfBkjTJ~8($JX=IR5&=^Td18flGFh@t9oO2`B`DnbJ!QbMy4qaVIZ7ndsDHNaLOISpK=_1k{z*@Mb z6@!C9tJjEXtAT0G%`s+j-+*^iYe#DMbl8G4Mnf{eN1plb#2jT?8X`CEY?mr-c z=+kU;kV{`^c7V0Zr7!yNgkJ%BM!*!ZAp`*HWkeOa=n}tlkDF%7L2xB`gbB|f4 zA{5*1=~vof->SNRoE3+T9`ch1(m9Sry4JwB7z+wzKfbX zrZzd3L#Rm0dKQ&4(=3*@jW1hKgi(hqTY4w=dwMCq$RAbZ4WR6PwV)Z$#;}OO^QQ!7 zrh>OC5oT|uIHWcQ-i%kcCpUD;!Af7eQQ;KKP;vLngxOn<>5~Fk)*klYUL zZpOjEEJo5vCe5?$n7B;8n<^{sr32=vP2l(t* z{$$6thte`~8I*P%@OOK$Q76k(m}&yB99c2|cMemNp(3|Bh+{;izeUwuQU>#$uED(Z z&$!^%zHfmOfH9u0iMeSBLRYz>>+Q>?Zro!_iP=<)NxEO+TBE9RwfN{d<7G>&z>oOb zR68-ro1K@zwS1GNpMmjm)BMBgK6#`QlTS&k+Dd{|CWzK^SrXDWt!;EXp3jRg(|^;F zaP>BRay>G7pqE=y>hV%q=>99rD}kPYUF;|CHx5lCcSmh5jtP^E_irVC=-bq2Ul$;f z$Z*}Hy?h`B65EpsR6C7VH8g*^oSnZj+xSXg#jA4S!s)0-@BGn#)|CLoznM(Ajh$CD z^y_A0|44IP$n_Qyt(_cgd zB@oiG91?>UZ0tSGvXF2*T)NZT>kFXm0K`=FAot%DY*+Mc!BjWvVt}+P?2|q6fBJx5 z9}uUpd}mOJ=8N)OyhCIDb6&3w(+*0BcoksZCD{p`MR&vtqc9WcDG;nPnid}axng(0 zM|bxU9WAD&`>*sQmWmiS*L384do?Llu)Ch7SeMedVLZlZMJssS@;rK*K#@1sO!6f6 zZu`+R6sfCTGCuD{+ZU_7VkN29p!uMnT?P=%NXOi^pCT7^f}JphpFIqsAaW`4r}v1h zt8I+^0IfN;hboPB*6pqOed)Zcg~MenQW-s&OYy>cDxGYk5$1Q7xsFy%8U>_5`7_D4 z)5L(c>TL3in)O!{G4%|p5;@|a_A**|$$kEEoo5StgN)vaL5v@v+Y+FxXYcVt+S5Fi zKknec=~2U=X%iE|OwU@P8w0ej_%oWyrn0hBCgRT69QB9eeB;f+^A2v+woiGc4eAvEPe@Gm3!i->Ymc-~T=%U9%=PrRxJ|aHj~Ca{FIDGuk>8Keg?;bO zWtmzW;P3Bjk@~6r&deny7Mz)!^Drp#pTll`w0U5u-l`!dfId~gAw;6O40a3Na=Xp;M=Aw*!7*-xoa(v zT%5NB@f7sVQ3eWf_tlAf;a^vyM5?Yfn+xd<6~n{FuRVqq=gvB7IYv4QkMq@r`Q2kO zyR{z=7YB_K3RuQO;HB9+E8+b|mk1{I+yPn24|aP$F-0iL$mw(PJ}pl_{1H7|F*Zwa+|-@?k*6MV zd4(V+lUl09`q|xJrL9NWTP*DunC!czZoxXKV6hwTSX{)c~r~f{dzDK|>VA?cF(L#kNRi2Loer)U#)lsKC>YA^iwnT7Xh< z;VKHF7OR)S#+PbpXXaxd$?$)YWb+>U%z#k2#F$pmD*;$#>orc5towRxi<>s6hfA#m;ZR9oAi+R(#U*n zx?0EZCEm4OriRY?2@N#kh*%1jj5OuWTtp_trR-JGNSyY$X$3Kb#P8YhFWSXEKyl8v zHy5#DfaeRjZS0u*RCD@Gb8Uxa+u^dp^in{~T^+KLowO%+PT0Z4octO`T$Wn6RkrCj zr6gY7W()?ZoytsYKZ+3C^|b;oT$v7HKwb}5*dHlRP7Oidy8kVx>|%bh zgUQ)_REw~u8ID(eDc`%)O8i5=`WmD&HCq)C_QC;LxvhS@J2w5aR#HLEumA6d$>SEk z9v7K0JJ2(BEW&7i zTGcX-pr>XJt|OHBQABPnh&<8Q*jSG={p-Li4{26;l()Qiq~~6glVtZ*ci`p)le3cz z6g9f@%U@SRcy9|n-xMe|!K}FzgSW?4!=}GPWCwjb8x&}bG~GffU+f@-V`6uEFRMl> zJdb>T!Su{fW7se@6qfX&-=rh1)N*UUKa{YtVqG<1zWAo~@somZ=7kFphNu@ch4};~ zX0X(v$a^RMdi{I6_TbL@E24;VE2;9k?@B)W`)Z)F+B3~8hslXN)K1Ewsu z982dQ93llkmSk7H>fXJ6MIyQEfBFTIO-kYq5JwFUKX~i0UaF&z=R3>veBiY0XQ8fi z>7}wk_NDYzV>$ozT=;Awl-lT14A%P65;;--idh&~aurh%acrUpMJpZDur|kP) zeu(7n3(YJRU-%_MdTgnl+LL~6 zF|4@wuOfaFvl|&^{q+)GA}O(NA=sNbckU`VfXx-Th|#boksfI~vGOU6fBYXehVHhN zjN5ycT}EDc+DseBD?&BlSx~20p1+M%SO*pCUAvq6*gSB}W6XLEpQUNNd;S1GF8_>0qs*uSkJ!6A64BT()^yNvGad1G{N2IU$$9;!WTR2!(#>ry33~cC z)2#h;XX#SbyPAhLJi4s5oQ3ULgV##wFF!w#o3FR(pET);^X2koeI4`+V|j<(vWK0C zvBWd#ap_poi=D3wX9$RK(EI|%SBOy0TBaTa37J49`Hw1tvyj^-7X(zhyeGiB0PhC$ zxnZR^KzYuq%(dpn>$l~Gkx#@fGpmUqsNz z`)f&e0!SN;snx^>g3=dOxcN2AS${)TbL{)r?j8XY93HR%0Eax>M#2?of6 z3=Liz$HCSYZ)>;s(M*+`aji;2pD=chuB&N31(pBcFr}g1xn>^;g%?0tSF*I5l{A>VlVxhB zLyYct{=v3j|BhG>MC4l7h z@I-FfcLtwEQxNNMK&_T@F2N33EiWm43Jo+|q!l|+-J?IDFpcX4=ks0!OQZ@s!{wp4 zr^-iytFM`EFs~%SR2z$sjUT@|T+3_K9RRhc{IT7}*?QkfjrVw1p|`-##cAV0C;$0_ zwZ|BslHmp@KGa+OF5rahAUxO4lC zU=>0b!3kw|p?lNvW3r$qL)T!Pd--SR#WGQgS5vD=i-tkhXq)&Jy=CXosxeh7tmiVv zy^;F%=(Gy{^xKjb>t~W6$n3&_KGqSOQvlYgKDIk4gWTAAQmX-fiTAn6+$?QsbC?O@ zPUbMb^>rin8$@p43o7%0Kih6hzK~mCpwX5;1d&uVk5A1tebJ?mBdpIYD0s*$vE!&U zw2GS_do|mjGFJbl36u0I9dGtXskR6izMQz@oe92yH%}I{PkI%T4fG&D9)PHfq2mrZTRx zrCYVWRbRRomyaLB)V!8mFj2XGbVtBl@JVyspma>I)(@KP;DUs-$xdWX_{D<-Bg635 ziDO+&JpV*&mI?YaaC83k8}aPH`QaNuLag;geqM%PqdYz}xfR0Cv2LR>`iw0;E@9*o z=&nY@qWbWuQ){y6uV!iSX7Lfjr*`29fbp$^K?yX4=wEhP(tuiiC2TETN7AUppPL}| zuUDM7KMUG96ntCyu#Hd9+VbBW|4+gIW()J1&(A!y#P@umHjX{nqx z-}{>%3hxOj(lH8nnB>J8w?4R|pE^oPvU>{B|LD{C=K0Lc{=WB@lH!4V+0SJ_oL`ON z(|UN8(IJ=&>Ti|lMUpChT4zI(R#|h+B+&&meU!fA-4|O3!M5JHqf=Ov8sseXZjk}D z!YCn{+GP^=`a@834|}k7MHn} z0;dybXZju>>H-H;1WwNzz5>>fVy#g430fJc!8TF>?0$*Zdpos$6Q?eY8(k4W+x4vj z#7&Uri^wk;&LWtFB_9||A zGxdwGsG!(izr1<(82nLf!k+r!on@Ae%VIlA4;eq;vhl--Jj-2aazPX!5U!tX_@kwc zRzvqyj(9EN?`MT^aW#X-O(b(>tE4`E{oYCQj@*3rq@5ZHhP&lQsFAJKuw^3o&Y1}U z_K9IVGovnR#|fVLTw|*=V%+$%QHX6)ToQXVpv|MuK~;HF429Q)zi-eUzB47^iSKEA zv6=%=aBQ7Dy}{zqYQ4Q(OO}7+qBRP|3u>gK)gk+8bI7Y*w0skuo1>90>HCGxmysqS)#27|2#>znU{O~IErCxk6s`g+H$u(Nwb-KU*Ff@Cq}yYuIU;p2n8`Za>6 zoyfv`@zpomlOgk8ThBaoiSaD3xm-j%YW5xhlJ@w7_3Q&W))~J z3+L}vHF|_(luYjOZ96`IcO4kc2ATODb;+v#l@N6q6@4YuK2{^5h~vIq^vcS*@gd^+ zR)dEMmiOa%z=C9@M4oo}xzlLvo94+OhAvM&L4)Yb20pQ=`+_1apVygUyS^6khu=1Y zs863U;!XVFH=K6F5pnnK#{-veL7+I2N&cZ0w4jQifyUoLmjBQh#vm3GCVQ8nIhX~l zpjV57Xx7Qw9q~zQ{*oQ^M*`9!w!*P4S$r5W)rjpYb7VHE`2S9b+pPo4q?N9KX%Y5w=Z6Ox zlQi-s%1Ki_QAn{%?S0}WpvwMe-Ric$IPJEI-$qC*uNDisxhhq~t1a)X?9k)+HL7U# zPnOM7%nhE&bK@7HS<8PmSor}V5s`0md&lu5tgZ?t4jDEV6y4aLCYK{w3*F#QIQ zGjj9vI89A<8U9oqw$Zuj@h&$zufFJRTI@_=Q9O=%?Y~M?-06GgC9x~i#V(lA&g3=h)V7qzGf)T8iLi}F~Wp9R-!`mFYpR~ zkB3Cu<=i$RHqhF}@np!*RH-ImRN`T=a571(81U?4&Z>D{kKg#Kp}xm?Mg+0X;m_JA zMYMC09|nr|e}b&=S(_9`*bY#jewI4?WrJAF%qfJfBk1;GlJF$#KfuJ}R66 zbm(7`GQ?JpZIsu1I)3OEPziYD6PvV|9>+$tUn%R%%eO)tlqKp+IgI0 zq&0jgw}X9(4@@ov{Qq3-9cG2qr<}B(SLZN%fZ3c5UD{wxAMtuVY3!}lpiSf{HtiUb zKoIX7zFDz%c%;LBa|+lX@>v3b-IS1^tL!M5)TS*+tMY(H_I9c}wz^Z&EWMAD{EFYK zjPxhDC9U2W4=el#yHF{*ZQ9RWNK@8(lAXe14KqHhn&la>trZ!_8HUgniWi5m2Wx;S z39~_QvUqI{!9Y8C_0Ck|Ds81d$EK_uFL1-Auf1SFHP}OALo6brPJ8%3DNk$DZB!Aj zNjxV!c+RKenFvi1IfxG;dLmRf$QS?SySeS@Y>j(|^JQJQEmpaD^Vw98$y?I7^rG2O z1zI-Y+?sh>@=GzGb4@DzvkOBrDroGrctZ10}^BpM?hq$;* z=iD68+gESJJY(gYtM~nmOvVKy9i8BY+ee^}hQ2cj9)kue97SK0!=VhAloKd(%-XQyB zf0M-6em^&0i?-!@S9V|PaSOc^bK>5%2mzfS8sR3jwj5RF7xQVBcYMAE8X6ySO>)b% zd5b60bcuD^x!rHsme~l@dV^gY+jWEaR^oY%$?xkVzhzTN@!0~dEJ7kmZkJT*L@eRI zOUz`J#a@gaBz{9A9{|khIWK?r`=b6nfFP6j`|0Cu;d!aoKD?ihJoJ^ls!#q^A)WwJ zyj2`=;!zhEFUD;^tmrhXOjWYy?tYu{qZQ6qUiQ$Q$NL;z?T#*Fx%%+I`P*6w@1Iog z*J?}c>}A(!C5JrXe4NCe^6>Mr$%aw;#rJuQ|H3nF2Ye9!;d^nMC_x{1S-tv*-`HNu zi_VByQPgf`ddgEOJgJX1Xz_yORg8WGj;yuxGP=Cyvr~I#PPKBFjq14{O*6a z!RsVoL0dW|IBe%P3r}fcSG~D(|@GCuhS6E7Xek;9fkIZ5;v;K?v~&)575+r zAA}dWy9vm{3XmMyfTSEG4a#`C>@$@PE=sK5bEOgev6%U27gIjdfYv{6*Js!kt~4?h z4}W{~=p%=>n*;ZCnzK_?nF`+cL*o!S1qT=6OYW$c&65<3%I6Bkt+WFW8ijqJJ0@CY zZzT>1^;VZsY0KcVM>n^TkF7zLKnIQ0%AC0*XlIUJ+c)ut#nf@Ge%gYTcWfl;?Gk~L zI41#Ri8p+r7?4zvmWBaAQbf8L z5T$Dr=@=TMr9@&-B&3nfLApB!X6843&ikJ4(f9nW{roXp7cdAYQ`V|XY3rxoHB;r=7x_GAu zwr6$fb^Zms+Ubnn?PWx=gL*rs^7H70!T75tFb%JcweY+Qdt;$h88(gX4Q8UN81K1m z^cc?mEKQt0Ia8Hoh7imCAk{W^*deD&VlcC}<^sXx^HT?`Z_CEPCe=-Jhf$)>DoDkQ zO+|elq%L?RqY!qo0T}5u0kOEy)wO?eObFcitzTH6?%#dg8#3$06CRS&k>8fu9Jdb= zxhyAqk@CK8Q7IO{GPhvlfuz+N|Y=$)?P2=Bul%q9R7$+z}U z?YHdeE{X<&HTWksQy&$vyhky7hC65P6&}}<*cw1%2p%N9D~s`0yT1(=CX>2~%}GE0 z^ypj8YKu-@=JGb~DPwo)h3Q*q@m<~6Q<~pP%*e&?y*P_~<#WR-c7j(6HW?iAR=aCy zt1^Q>VH|RnCpVo#^EVTLr;$a{cB%UDXOV^p;;Rp7!!G!Fx@xtX7y}H(@YbARa%8tk zUru3lA|4IxdB_D}=VIZaRRB}(`unzCz;BlmY zR#n?EP)+Gkxdl(oU>HvA=GfUY9_N3}pZCA#ZyO(5sHV(+t>@JC*INZ`$9H8l@iWncB#GdMiY9XIWQ0m~gYL z-lnX!uA+O3_=A4@2K_CyujA@U`fMJB^j!9*8qGI)boxtJ^Vt$s2p%?RDDXzGevi+! zqO$XrEpPQb)bZn6OSiu9B4ApxmssMxd&69l^Hz;}eEf3Nd$Rkl=%N+<%L-+RU*2-L zGTvEnTyzj=XP*}DLrwv#Q>yi7#vo&qpSs;=cy@>HIfcf>yif30+#;=J&fmNB>`@-m zW<5j22S6xIb@rkKoJ*LuF$yLvT|41s^eTy1iK6k8;0NpTddpn`)%LS(Qg_a3)A#mD z%UxT?PaM#(b*x5)3!~jUzwKOt334tb(#F_!m|G8R4tPTy=zVclGG1Md3A@l8 zOqTVjz&A|vWP$L~M^-+7YPi`VYx9auihq{=L;AK3CR zgQr2^44jw&4W=9Dx$HNME^=wD8$#Y?b4SfFO|fXX~e;!IareG7*Q8jcG}nVHCa~H z?dG#BibP7Mh;hp3FfnIVA3}9MsIs`A^+3ZqyWfFe$;xEgw+o=imP)62_#Kkh_`6!e6!E{Oib)FSv9o<5 zWEzjLNm+AHh?kCWQFsqXoxG(({nIe>p@p)gL_Ii{oErn4JT;r zgMhccxC*WS`;TYOU~Gi9WB~87_AdK)hpCt#@se61m-Gc}!wZZk&)%B(!^B2B-BzTRwJ0)RF#j)iak3WyBvyWnE^6-KDvFp@FOK8|4>QR zN$;qFNH!pebH?8nck`zp`-pME`1)JPU0T$G94M9NA-631V5PKLD7Si7D%m+_468(n z-vfM+y-r7Mo$O)DIc7pF;UMA(#EBzotAXaVOUUabo7HkMC4a-gVWnN4@#n)Y!*`pa z$7({5uXjGl3~Gib9QV@8%HPflp-;3G5z$G&i_WC{5ue6jLiMKnZVz?7{qn8%u4)TV z+}*el?P8W$)~ePQ^ckDi5BI<7@;MQDvbUc4HNlOr2nmVz5l_v*T^@X@Ez_MCmbzx5 z7LqNp{DWMLeVhNpuXE-8jGZEpS?YsUi5D**Dw7P#GK3XBEc}f7MAEw>s0~~) z6{oJN2b%(G2*YoFIyTxI&0+@`zZ|(NKD7C4E->GxkKd6S#U9-jQ< zt8zc=@4Uz-H+zCL{$`PxtY%hpVj13a-6EN~@Yg#;(=|s*?w@^5jOP|$kUmFJOdA{iHOuV;$;wYsh zm&KZ|f!V-n@E!cFfwBFM)-p^=?)=(SEg$mH2#V!SzOp*0+AGf1UGLM|gvxeunuD+E zK0ddv%eEO$IP22K#nz|^dhZ~qZKJJu&-w(LN7ln+x9nKB^kG)G`z)f~!J!x?fz8e! zO-$Tu#aSYJFY(SYXp3;#x=Ga@O#9kz`6z6@56`B3^Zov<57wvnJ33hm*lP%OY8M@# z=n=}FO!ewPThGVh$FZ6^)+T2ylaa|O!H@cub~3TyJ~?v~yFzCyz_uG!Ppd7|dUTIr z$UGL*TRl$j!=$ZZXu5{bP^8w;$c?3g>6EYMWg;sdfJ;p9xJu*42MB(6&`{eW7l=T?~77XT$_- zE_-iLkf0fRC2f`5v3xHL{WNBWKK$z%X;z_}W3Sk+*Ij1)pYw1#+2uLumAh)0 z^IyFae!b+DH^aP=Kv8@uq>u2*0vmop_D&2cdirdycL4%@G{~ z$J@UM>e_p!-OB(PWgHobp+`*oBu9P79HrVcLADhhK2h|{D;Y1&?%){fZeimQj98sp z&%FU3R{uA0+rJZk&CB1_mwxc0sDdcAw}En@zg@ON|evYe#i*`#$m0f&~(SiajZi~Cj=hdsXD z%bWJ&NBMEfhfdmwpaz9mfRcYw?d5klcfaR+!k)2hygW=p3%W(%Me%NbRw4r{R05Mv zGPM=Yt8ayEhPGE>sKwhf4v6}djhj=r-P3InLu<0dC<6LoKG?= zkOCL)=1s-jHxwNb5k$|nJZMC?A-1nFXMQOvJa>_~T(_evqqdC?NK*==Cd3R zHB1(m^vpyR18jywMKf;pV?b4r%Rl01n5h;VikQr_+LY}dN^5~HevNIoca4u~9oN0}%S3FFZFae|98Q zOJ0^$KC`F!vPdeWi5h!Y1L?Lz9gxez`If!hAUUEO)8r4bt3#VGoY-yWsH znf&tI8Q!f-zuv>%Fv6B2_9rsWLM(hjHRSnL9Q#}_m1)Pcwy^l5NErCz?_xCChI!KiS9rn7&7tH&$i}=DA4RWdb zK5^kI6ED|w)0=tOtC5*EJuQGK@vWUa9Zc4r?}cKFtP{Z^r^TbXeDssS=D5pI745f( zpLjAoC3`ni(mc`n{_RaCh~Y7)_G;PIv2mA}IpTdiYl8(36Siz;W{As!uJVgmMa^%f z;ZRCV@-llu6RiLhz7)&!GrW~7zuwEY!HNa(LK&#`mpwZ(4msWlM)Gy8y4#c=C%!OT zMHWhwc*4FK#|)FaqZH2B+`(ylwiVTgm!PXr^~<5tcN}}{##{)Y@NiTR(dOdgcPPC#Z_2qcgryT8h#_}I=U2;6>nPe`E2e7Ab{{*R62>R~DB9*1sG5z&wKAGsZc z%xQhU6@+Akm_Yg;Ab3>(-%m%GVkbNd%<;8Ek?h&?4x1NA$X`p!@W{Hv0^)M4J5@lk zAi(`w*KAjs9WR4TM6fN*7v$^f!2Jn<(N*jG zefiy11bCx6E{UP%4axU{{gL=E%>!6FXCjKTlCyH`@{J%@@*+gYe zo>CwD)uf*hd|G&r!+GM&!$U6EY; zmv%ky`$X5~t`&SZAwCIfJ$85KE8zXNX2OMMVxJ4B>j-RNNK!WwI$gR{m+|)fvO3Dk zs2zJ+#30s(NJTu>s=~J%RB!*UZPEC*v&GxG&!NjqQD3OlovEJre7$Qd)c3S@;RHCV zdg*(HEd-CvQ&1buPYC%htsQ(jv>Xe5(*5_}y@+`EbyGUc19p0}jATOl!%w$MX07Ss ztj#iIDkvM4K2nxU)nr*!SAdd@-R##B9-ycGYp>eX+3#ZYKHwJ9kykGENCJ6eo>?Ms z`FGY?(%bmB1#~{%cq{U=|9@Y-hiBrO3#k59`=VKRY;79RY+r`|-Gn8kq0Km|f0f|A zhWcv)V=W(p!A~ZE`vq@)-;PRSBxm)$E~aOIT!y+JHT-M?p0OJ{A-D9 zt2-amt70%#N$~`v)tUEj`RwTdeD|eZv^rsj$bJLMbdB-%PlNuCH2sz?v>YDK&;x`3 z%agFYNhsvvO%|_CJ*P+9z)7MKxM&rb6KPk?r|WtLi+!0p`7aNLMI67Q1g_Ei&u1^Z!~9%@#HC{_ha_q>@VFHJ zc;-JS#)k$oySfArCn>ygDGi#e(rbhbD#GI^ahl)bkS6};qv%4tffpJ~XvM^YGQah} zL?n^!;*M!i=&etHiCe9#%nQy#e$#QZ*B$M-d6yipi9lz)j8r%I>uXxo-JP*8)hdSD zJHWD>NsrYe*#)hM&z3{e&!GewA%8qy_;87kzXnZhH#i^HnJI2As}muK?(!`XfhcdD z(iOm2c^6EU2Y>m}%~^~6**zT6F}cv&M_4Jm@z+=!6#*I9#`(Epq?9MTg;JPF1P z2G;9x@&Lo$206M|a#7LFo}LKgR2SLW3qsucsZw|IK$+xeE5ji3$QxZz=2~oLRy;lK z=gMA>gfYV9 z{OjF`iV5uPiaDWe@*CtQ0pDe_-*e&8u>Z%iaYo=IJe-4KLp`g}efcQYDlNE=pj+f@ z#<^D}B1vXu{05SD&YPL|yI>6EMAqb$)JJ&H>d?FOsc))_$q~nzc+_ zjBAHGJ~!+UANPP{?hab#DaAg(w3Ym9*b3h!yChumVi1kKI_!|%jQT3h{rA$H+{Gh5 zVcO`!2g_L5!4Uaq(N~}Tl2wajMG7LM#(?QYR9@jn8~%DB$2BPYecQ0zhB z3g3Q37A1C2h&8qbb6+tQ7t$Q#qlXkw=3jyj-}e4^U#!t&@Uxbch@6pq)o(pZdnJ+M zIA(?H?=gGQV?|vpJ5+(~>yZ%NgGH$Pa!W2vr z-;RSUi>-NpJJ4{8Bi?N-rDgWSeJL@%MVmDOWA;xxZz`6n|v_aeoxAluO>7v8{N8Vs&=G|as=}ab(Npp`V^iqt_olk9+MwuF+LVqnV*Dx*t z;|B8z6<~?%4Hw>1io5JE@=#uUlu6Dg5I^W^&Banolr2n%@#4Xs6Eqx3;3PP#Zr5Zm z7AXs^j+C9y+-?>E3HTaXxIBRZo{Y;M>Q<&CF_=17LLum5YT)^TD*#$Vr0Gy@y4;D`k6~#V9Rvn2h#zMNSq+57 z*{8Ye>~{M$X+WN`_d4E@cAPw1Qe$|L&G2Ud~;l+D^;$+%XW+T6olk$?Yz^};&Un0Ydr*qwAgyPkDE+TtgA`k0;oCKuuUv%siueXKP7M^%*(W$-~5Ktuj) zY1!{$=~$=$UD3EeAJvaB_t<>&K36&AlRtGJ$A9eo-#8HZ@qfTeln9Bmw1I(7;W8|L z16(hGljLxMKz#zaqrn*Ywz?3ATVul9{&Ua!sxQhbADtc4u0k`qHT=d(Krt=ebG5)p z;~-UNkd1{C;0#-VDxrCGB>(mE)Z;rgES!MZO?2{+`Zrky2$whJZ1S-RdNrC9$Rl@_ z*yDEv%#Ux~ae|42a}*4#%HI=D&CZ%ZMCxA;XjYai?>2Q*{iB_`zE zpH%@!X&H9-4mcLc7!{{&Z6<4UN8|034&_30jr3HYyAu0_!8QrOZ6~3|!K>2@s+CQb z96*4-K@;U>{`u~JlxJs$Dco{Mu7;E@F0hZe-V=LTJuq{+o<=MVM_E0ASSaS<>t+U^^p0mAq?Ev;1 zQ7sHYUlRY9oo|qVgO5GQe-tY+Y!3tPf+Xv-|B`5t-x5t(YTA#Wffo`DkV7OlNgYjG zTn5O&!Tay^(49gM4pGc}1TY2cZ34Yw^(yK??AK)U=*LfJVz?R5PkY_6_UJ&KW54ti zP8M5Lf)5q~famk)R?u@$$?{E0UpHQCU93Z3eicvj+;bm!+QnvLvBt+3#BDvyqRaik zOZy!5RX7S|3PlVIYofES0NktZ6J!lm$j%Ww?#Bp}*2=fOiaVdcfC1~Oy6Qbg%+)F7 zLzGWzFuQx7#W}6tKxe+n#O&sEVQIScg8MS!5vo{BJRh^yjnMGxGO$p3_04Uo+cMyV z(5XHgW4ZE>CzHdyKb22)Ex>sV6$07;u7XgGK2RZrtLPrDhM$K3U*rA0(euqfjQ8f| zPlaEl#Hx}rkD_#m0eq{@mCY6_?HpuLa!W&BM6FKg=-(f8Wn8PR2%O6U-QUtd1l^k} zJfC;K(5S+|ak}-G7n@{*Z+v&_Cky&ke%#-GpkyQ`F1Zy=gjEr(uH3WR)l1P|@|H%m zK~QwCsv@H8%QM@}!j$AQmSkYvn9|n&QL=ob!q|lrN+AEesukC>N?v~`S@zJ|+&Z$M zZO`uugaUq@?|c==7Eu)h1*(c|cSubs_NL6EQhS~TH+}4h*lRUfiJx`hJqm|7s9nyV zON>Ika!+cY>LGBY6?M#2RgZW9v@P{b7jIA5t+z{9s@5}iKJ+Qg21(d-&@SO&V?z~7IS?D-}&YGF^78}n| zu;ng=zv^EFc7r{zhc?v#caBEZlh#?60m(-u>+`^)=*L2|CP*X&w0XboZI75xi_Xfo z$K~wqsDU%;%kQ+>QGq8fPcbunUeDV(6A;{|kS4`$_{D8j*Z$Ss{h--C$XIUg>eoZ5 zX!KsdzJPnHfH!>;s**U`+&L~zCEI@~3fMUB^aOgAPvS1$%wT&0_Ep+9OBNKp^qXB4 z##|rAY~hjd-#r?T`p|cd&`x&G)uF<|;Uhj?AG;GmGVou_^P@&cr~-bFUkl@g*l^Vn zf+_vZpJ2J6h$q3Yk-}eCC7YX}!9EIhy2Zb*P!`ZKX>KOAkh5~75j5uv>sd--0&2@4 zb)fk-5l>bso3AZb#-%`lqM!5^uDZVH!Hiyh90R+LcLD<_Qq@;_<|!GPHeDNOng-}I z9Q<0rk^CxvC>D>+r@)~mQ|*kLgGjr|^_VfV2~XU+vy6Q&)V(6N-4QFFs)WxFTrep%2+n$iAvNlRjiy|{NZbSg8 zs?v_~<7dDp%nJcVE2d@q+($`dgY)tv;c=$KGZv|cSVY>L~j zW976)x;HODraS6+hi7K4G1u3}LMp&x${=jEN$Yk02HBmqKkO15QUmC}@YMGoJne*w zi9h)ZPq7SvXSngUyX-C+TI}`R2nA!i{?$+OBU5m~W?D>E4oZQUe)PC%IQzB3m!_c!1ptxlGGtf}^dg zF#;v>uFa=aKNZ&8=WQTgy)f^NP?WKBmnZXE2;KZM=usA>Hr2H2LZ_-=^S2`GYmSA0 zkg0PsD5uXD9RVf$Fgb}K)u?-W{vbXH0zVxDGSizItu!$JglLX=lM+WJV^NaKt{l4{ zwzlFP8+B)*;NDp$NX1ieF~}KX)=;Mrg2Nvbah=oicnD-_n^W3rGqgLt-v(m2cgpDv zQdv=h&nvHlQOa>0rCojK^Gb1CLqQ{4-yA;C2kmWc?%-+h6UWznd+cl1e zn-+Q}MVce)t6`K1iS9Ke!OvV3POeV3`F0-*mS$FWgYRAb#orY1o?@*O1WXhQqM&14 zD;W4KTvYro7CfB$56KIc*m*w4oA6opd;xNB^j@!K;?)2F#$}Fx7lRNoV({zLEsnfi z&ytD>T*-w*wru%-h+ruz?gn3;GZzL=B}7EZt!oZMCMX!5=K(x+MD!3k2E>CPuy!#V zZCW2NQQH0J>iha0z289Gm-Fij4=-1=h0o@V5Qx0zhq|1=Z@)hJ%w_?>mqW!^ZK^Fc zZDd{^jDqFZMf*(Yo{xb}CuH-a5Fx?9?b{MzEt6*f8kh4-AR51s)s$#+PleM)Dl81S z^#%YN8)DHZsN@evmkeOuqlIn3TYmAVd+jzu@*JzO`{S|Ko9pI6tJ@f~5A@KeDrU}Q zy+SqHdxR0HOt0U}{PH9AV#2h8)G?5Eg560~(~c%D=rGd#vhVGEMBmFy_ERI~$#XO+ zFNtyi;sagAl$xhB#9nx3Z1l#~oGSo#rQ}M`m92wA1+ii`Oq|3b~RqO8CQ` z*MK#UjG4o7-txpV7i_jZ9Evdys;E~*qxK@|TegH$gU?6Qfs?3lC!o)ay*4xeV-14x zButPaQ$^ApbL1?fJofANEq?~j{74^xtXD`Cv${EcT}9H8W*4I4C<`+1fW z{bly^*OT+#wch6hyex&IzIdp{-@nFz`hZ|*8y|veDwBA$0>LYrJe!Z+aPKBvV#%@Q z7C7+A1$sO<3%0d5bcUWU6!DQZOm9o`x_-GNflitidAoDuV=Bj78!sh+JVCiK`$KIb zi zn#)D}^is%{wZ*Sjn{Tlr+1l(bU=5g}yni6f3-;-#01^+v6ML59W%X8G%5F~MJt@Ac zahc~)W$1Xm|Fu{m9|5V%96NtEdAGOh%ZE>dhKcPtli*1@GX3JM-W#@C*4CG+Mu~l0 zYC&w^fG4MJGr<-XN;6j{BdwPp)TZ{xQ(NOwJ;Oe{zfI6O0M-O0KUsBr2CG7l3lF=( zL4tKEe_^K#fs@ei(K>_N9-&9SuoqB5|@cO=DkodqYah+I?^dI%gQ~Ai(@ey>SHF3Z^EYf_T6B0Vq=?cgKY(yFpiB*~B$8R{PM8^S+XA1;jp zA$EF6_$hAx=7nMaLM%DF;Ct}&&wmR|2mV72<$}J6*$33tsL#@zj>M`xv7S7CAZXeI z%#Z1pc39^hjT7BrAC2x86dfNv7Jbf;=2}p`8x!{K30H#V%U&Hn!Y<0u`e18qsS9@H z_)l@mr$94Y-ORJMIFo4-T!7fJ19K>y==nek z>c$$!=dT5@7nAm{3LLv!TUAS+dNi2_#nr2xvC0{&AS%BYu6zsg;#PEJsoiI#s@P%B zWVrn~&R3!BTJJLG`j972^m(r>R7Rgg`V}mRnBq2<`@!N@;IZ+u5aQeHI`jg6YwbkE zE3EOkk7q}OyjWA@{yjRbqNzZ`5IAHvq~f*u=3$gEpL%kptVzj54fVCd=prr9%P z8ZkUXzI<|;sofdjEkQ0TO@>qIbdlr3bg&rVQ~f~3c8hPr`kLhNElrT%RyO)>G1GDQ z>ug7>%M-5ml-(}buK;|si^OMboV4-CqyRSlTl~v62c_9Sl5mfn9?oU(&srj&ET?t4 zEGKN?`gDji+`067AS}Tzmo4C&jIf+Z_eovEIxZ3W0Q{Aij+*TMack9yVFduk36~L1 zhX{EiJhaWNS{cc>u}q=-mv;N@;}IOz=9Uwni-VvGg-?URW$x-pfS1ihP{ ztn4vghcVS6F_z_O)POJh;w+aoJ`}1*X!~37P z>1$*y0Ieew6S|bsOJaD#x%D(RjX{`B;{H%RY3o&znJ~{GO5ZC19HktFzXvj`hIKt! zWNt0Byy{}b4+{F(&T~x0D3_>%bv3CRTni6(>CGBe|oEa+vto>Cb-u zo<(KWG|v>7n}<9nrUC=F1)2Ip`gG4ZVa)fSxoXq);ZR+*fKGDJTUjT|c`^h$8uL69 zpJ}4dwK*2=ogUQ$CIr8^!fpMeoBqX!5vN6u$;&=M^-rL{rP(OO&Uqtp(CkgLq6A%P z(Y;D5g4uD5+JGiJb1^W|z`=oypVw zU(8h7-HL$kcmIA1EE5MG<<3^i`&^}{l#PDa|FqnxcpcvYEBKM2!COi;gnw%v@yD<= zd{kno`i)88FBZwN2o(|Fn$F11n=-rI z^_yvN%BS}ui0!7&UygqHke|qZ)8cJB#n&+d;kaKn%%4?$gIAn4xgR>d=kuthY>9;N=4NWIIIrKQb2usM}>@({;*%bSJMG3l4T=78!@rmZ-1Qjvu ziU2pM4v{2Zn1M-Dn}7Dy?vLB{3}RPEEI1?oB@SZI55re+7CX z??)~S(U{6;{eCPNP!aD+!DqboYNKH=W_h_Oqn23_T@e8A?+rw!L(nt5ogQ{g4I~HX zi3t=;ic5!#VD04id*+$*X6Ae8#~3(3*eb*`3xd8>J=>ivE-WgUjK|7nL_58~F8+rg z!ROeh2tZrSzPBNG(Rmx-8;=NtjsLWNea*Ddcw*wHCA5u7d5XxSnDCOvYk^zrXOGy> zlt_lkpyWh~QKt6+L`VVd@fv>#z($JsJru~bh4&4;i7ho0TLY4T$%a>v2yU*h9d=(K zB$@bPLGy^{(0dphuo#1TqAfbu1|F`CS(}B1sH?HHwvbzaI+}%&B1q&Pu3LWrx94Dkh~V3L=u2S+tx$sd z={O2%J|~}54lp*0vG!6W)A>pp6QeQXI~uFck+PknXmH?XOBSOTvt&N-|COTV6DzMriA z@!|n)g_7MA#qQHD8rOmV9Bhg5nIgSQPaqMJ_d)r8hL5nHg|X=Ae5_m2s)&&0uo9$` zcreHyju+&&#FtkychnbMr~E6U(jM^8CIP)uujlqsIHvGwQ~qL=_u2qwSE|ouv~@s? zU}6n>iCLIDS$ivHL9V~Sz%GthN`H3&S4!t#a{TYGl?w#iAqdp(_ zL!VG4kzpUHAKZ0J8_n%)KN-l#Pv-GEfqy`br_DcoUGolxC1`q0*;K&-+avpr3LZ90 z+Q`gqQ;YsGp)S1{BG0MC=Vhc9+U96=T~xnQnGpQ^!>TI_gDPNI{Y>^`xSN^N6@G)N ztH%0U=|yU>$1w3i^>$`IfB2y$J}PZwNTrMDFW}}3@x2q}Q}=7!Xnxv>Ya$mbJF;_v z@i!|t*zI6|RB@kBp&RMWHmra2Hc{~$*schd+_z7mI7zYecjPd=>!2o2uryVc5Gfx) z-tsgRnBFlHILi-EM|K=398+F3<<0a>Ub>wUEx*9s*%YFa$j@oi3??9y4$;52OO7XZ z=RyH@OXQvvaddT*bRCQquO1sAxPNuIy>aO-hYcLOQMAWkx)ni94E3I!iU9C$>*1`)7KY$YPOsS6 z0i8&Yg~xl5gbv;m$9`*n^LRfoA~wvHW@BevXjDWJ>%lR|`qxjuw;Fxpi8ihk5p!o4 zrRVb17xG=!9pMkP;S5|lid#%SZ@WuUoeipwkh?@lC?it0i@oa^KZtVxRv)GRfUF&M z7oxt?YEk)3UL4K<0(M-mCFy^8C_J06{3Y?Q#mq^5N{OsMCOq2&2HShSVb8#n7Fd$o zjPVL7e`&JUZdqRliT@~XSuwr;0$tw~hW6zDf1^g!XfvxH{~q-*x>^&fIXpulWX+P`P?Yd+y;~)D)tIXe~JkPbGTQ@pV7)xoG$@|*p3;X zUdba>LK2TIOS{=<{gz01qFX$!L8@!p0^Kg8=Ti*?E00W?TsM=0Nud(h8*8Te7*5DT z?-oow89IH8ib(KY*ZO#yj&b2cxqdr=j!cJA)U*2bq|nCU>%WLv9}U*)lF=Cdj|Ws# znI)SV7O@2@p%lkQ%>>$v^8F#u8zk6$B>UQZyL;nPDEd>MqZ-I|V}1^WAHkhflE9yj z+2wJ4Gz512oYv#&9uwJuu^yNc64&6I{bDS(tqnp(*5$mKs{wY$H>=xL8UdhUFRI$2 zN)`+WR3amlyTH4^muepFU)r)sap3uxc1Mqse^8=q$BGa)9qRhwtfM)N`q%h?3+QBw zxwO6_>n8xQu(K`!M&#!0YhC;U=G|W}I&`cV77|_U)BYYMwPIkWJhI+v`!{t>;dvJt z?9wW-N%6*P3SOH;_xq3})6cZ-cd z{qz47@}m`z;r&c0c6ILmbcu(aafP%qO9rKdD)e}|PJ3ZRx=(vv=)=xu=^gpTfz7Fy z?1UGT0n4|V^06_jVOJ@4S6JC>;{=Jy7Dm3S6$IWT* z3Pq!1xWc5pC9iAUv>=cYv&;cBGV@o0GP~ySQNHxs(B&P1A3Ov4%vx(h&0JcbJrone zZjj>)io(E45H{uFqWf{woL`E(3fh0ZUUjc=@+J`4BsASiRQkgIM`!Y()HT#Of=v$@ zoB;W`pkRc~_E_xSU@~U?cK5ub4`?RH-U&sd<$vR{y!pyvBv18AMx8jJP&H zwRIP7)mDg7?*cgh7Ic2E@G`{Zr@b2ZmCW!Uq?en}J z(KB4X2QD&{z+jgACr8zyBiR{lyK3;UDd0-Js9gA3y0b zQ_^H{GW`S138BI3Ex0oG{teCeK??X>!?*EBT7s11EN@(YWCuC?9p!vTlPpQ5Ia6)k1s3ByoN9rvM+xJ z$H07_u-@(-)XlL`bc)#)k14u!uL5%bPlqDB&Fas?aw@rY%g}0KCjonv{;fsY$w+OG z|Kk-rkm-XJDSgi&RHz+fM3vzKk~Ut(*j8E-!M(T4a?jxQUXg-7U4&-v&pe{bM}K-@ zbzJZ+9>8j9l*0X2pz11Zmunfo^Ev%x;(cP|r4UsMTko)X8dFf>2Fs7%`Cf8o1KCT zJ-$g3)p`vGE(!QGIrECR6n-|ttfGZM%0r7YJqvow_P;!}MOYF-*Rx)|>k%9LqPTKO zDaY%+nj^$_G=-wPwX7~gsN7_k(q2LN%<&#w z9}iOd&9nMMWtKr)q55sUMk@*dySztVyCpa=$4`T~R|y%=&>nzLLrG7*8Y?>uq9@Dm zr-@$MerSa1Lh!yXv~{fYt`v%y7wpyf9H=d8roD_=%5c=P47lh54uQZ%T>R2tFt2(S zWx#&D`@X-COJhDA1R#LCmtw-FfYx-uy&b2?zQkzDeiCMxkSv3iIeUMKj{c{wYmjV zk;-RUEd%6BlfBn&)&&(FpQgyi^WcvB(i~5H6 z4s-?QKiQOWBIGA#=Uusy|3;_ZA3RmD#IbqF!nt+GLUOoUO1bdekO)4WUjIEPZo;zx zTN_uuLD9{o%*_sSt<$A3KXFYWN&kdjfBcu9lAV`&75hJ@;DXkO|VijI$Xed1<6g}5Cu&I)%tD#{Jq7QLe zxt_+!Eoa)5{zQk1~u(VGI78 zjRfql=dx`_hb$&PpR+Lw4FL<>Wr{Q}KsIR@vA|ea7JZV$t<>(wO6KpwABywb{hzME z)M?w-VWw}}F>vS8!C@hq5=|Tk`1MmsU6_T04(KzCGcfsJK`ONvj{ zUu*{5Lf4olv3Hi+BC*)6Y|AqliBmd}>{x6Fd9BUN1R(4kmD5igJN$1y;YDAC6Z>*3fVm zKWTfY)9qFQGQ7(L;(=iujhR25NuMsHyHv>=61+h)QN}+%3{^L_4iP=E#`&ZtKf^ow zUXdj`;;inGnZ;Y2g>#EeFmk$$-~#Om&aStsXokZN{VFFuj{>l+g;teKw253Tgzx!-9LMvvpK<&iHkxx%(+Jp=rU2~r4BHgqXdSA{o^aUgbawAA%s#@M`U+`jl2fbG zzpAkZl`|^JsvUjOYo7dnCMT?wr0y&RwukO~5a3qGiXB$bX%Brh2b=ODCI{2P>l=QP z^^sV7-W(6<>s(|Ep{o2tJ#NDm1!R2+co?gYo$A}#!Blr)CC38XRUJh z@LSz{)P|K2I*s%EtW|T^#y~7~FO{rDYWGC#AL9*RgM|pWlx#~g>{>RwUS6L&1dLz_ zB&f+}v{;+L;gwOQW{fJ72lfu|H%3dXsZAJ|Bw-KlVtBo5(;Iz%FJHbtFoe8nHl#YAtNJu z@0A(Jc8x;z-mAD=^Wxs?;=26bKHuN}`>W6IoK86%-d^|pdcL0X@p!!Q-vz)Lt*DhG z@gQ^O_y3Hdv~7q_d<<<`&HH>!Geb&S&maHx-;XCHAq>=Q#&?y@%K{G9nW+UX_4`&{mlXhS z8M*&p=W@U!0t*TD(ec>_CM3D2OH5mhS?JAk2=SF&zs%3V6dwWa1&nn5^He6Jm|AK) zV6D|xo&EfLr90&O42;-3=Mlt&Qu4z0&D)*j6}QX62* zme<#^AU~Cv&Zy?UvRD6$4YI&EwZ;Pk3=q~Mfqo;MtjB*l?f+>@1O4^}vn&o8s3*~v zK?Tox|H0K|x}zl9Bu^i(+p`NITWt2IJ^Oz_RL*z#^SRHVhww*;3_(cme*TXm{xecq z(-slrz@x5?mDFDsb2A6=`tAwY%*80A9Q-P~?SbP;TNcZ?Hk()|Md<6z$+=c zIbi~YGqj8>;3hU|CpeJ!H`*ZMpIS(r|NA+{t^q$V-ohrERenvb*1%7bfLnem6@P*` zzWZ;&55TCoc>c`|myHA5$}SEWpL(&^{I0;2RQDLqbr)CfP;~v(4yV+R$L#&z}B>%QQm$|h756u*8HNI zh1xd4O(p;IYQD$*?+2=6c>!CTx;6r<0bouS4Bz-mY$w_<9#qXRA^Dlej+3=0SpKz! z2C{#QIJ1g)e*&hnv=(n8cJYvr6Xo-*G-5|!bVs`EF3m1{f&_{JpX^BIu>QM=*?HbU z{=%YL(@c<+qed&`QT&v%9A>;{MN|>Ss`14{4M#X!03Sa27s6oZ1prd6h&M%%h@%Wm zNgCAg3(f^OIF;TOAGC=;1-8R*R#v)~x$qOXr8)lK-m7~r=%-3DXL32N-H(34=raB* zJh#-NVo><6ls8%~kr&uU1E*&O7s$_Mn2T&?aKR$4e3vd^JMCMqOQxw0zSmNs{H1KU zb_jN-m40QL)*A_h?6F`m!rrA>$*zEF?_%H7yZ8$D|T*l)e!qT zx>WT2J`W26Ozv$0a|^?P;G6uFkV2SypDze-ZL1bD?dol`R9C#Y?wlN2*-&M-~@Y5T) zEYP+Ydqz=&8X|<=U`{@2r!8yx%_2qK+ymw3XsYGI+4!B10QJXh_+Mj2f7PtO++P-W zKb{B8)I<+hA6W-i{rpDe&{it4!lO*ha{fD6#%46g&c?WdUB{_0Y*!4`(E-H%-)1l8 za{cSnncO_zEtI;?QIH(yK$y5i{}v13aVvzZs)f;?FrgZ;eC#Er=b%k-xyYZ(vAZbP z;6ub~izzVG*y)ahFc7~>dCiAp7MeVjoCS5FW!J!&H4N+4bh+J1qZ zwZ6)K5e>B)f%Q-BG{58+^t};6F=(tsURMAhKIQHWc}7u(|K&3QkhLlhGEE`sR3z)) zXhs(ZP%G5-3$3?R@pCO)D!M%_YO_6&xo5K6qJ_-9q;o^;u#7g%dXsMdLX12)8@bP_ zJZ{M8!R=I2J|C`J5EbGy*9RcmQ6fwFq-!`F5VkrjG*s#pw<`mxj@#bh3O~sn|JzHt zF#;#_wc@-Y2?vM2H#IdTUS-KDMvMfC%L5iH_xJ7u3;-vDg0A9{5OIPnO|~il;a>W_z-mkX-z6ylsPK(>fjk!N07|Kv!Asz z(IQ>%GOr&#;=nrdy0UBA=c<2XfQct6Rx}sFrW;c+aSl0| zUt1Csd?kBnaR1Wp7J0T%j7wR80H(ZsSy`&{NlQ|I&4mf!zMD0QdSeOV?IcEAxltCO zT*U$43H5)V6#r`~+9ZHY@mx(AP5njOdWD>{@w&D#6Okpw*cCftsf}MWBv2|jq8rXN zVG$~^0WJGP<-2A|)R?{7e{Z&&=glDmF_eR1x$}ED5yE$YSW!VTIy={*ZbyfrGCT2R zKRfZAqN8UyMkIIXbjr&)KOd&MN05Vk3ns|RpRt1PG0)t&PXyt>zOIQntwe3v>W7T( z4a;=*omO7#r&bg#%oA|$x?H6%-HtHR*8NUGF-cGAvo<0H1U-IfxFZ% zg#+0g?(?;|*03rcvpxnd`@}98Oi%`9X|Hp?$uk?h6}HCl5eBPPzjEjAJ(44i1U6ii zD3~PSj>*zHf9mbxx4{9jLvd>UuC2nKX%`b_lZ<<|Kx)WO*&kNpth2!USAn7B$kKzq zeB;=y06~D;pvqjw^}_PD)pjGXlF~hEVi`|{wd{K9WaBtAsUGGH_chKy89iz~L;jAK zD_qI&>`Z1#Scjl++XNWs-4d?jLuqdDV_i40ktZOmfM}{4j^>8ILP1=!WT$+yJNd!$ zW$NMEW7w<48J3v{JkccBI@0y>!*|SrO76PB-V0Y(iq+q~y_@~gc24Z-?&woMoFriF zh7pOND<{+C%R~CfRCIv_5@FBL^N)p$s2q?X!K%V;Zeg}vgCLUtx;uYQwfrv%0|8G= zlo}(F8MDe#Mf`BI{My;g`DWXZk*!t>w&m=F2A+G?S(chH0Wb+r!e3E3OX@1pCEzsg zNktD-?iSogA<_T>5++PheH3 z$Z91f)aAhtl&&wQHN})y8Q#1c{!!y@-ztubRy}Gbx-I$AqgkRbUJ@bGuKMm?hU*YZ z_nnZvUX<2Fnrx0bW5o!WWdqI2sI4CxVWv}0-mJHLTx_WZ&ic{YBaya$eEzyuu zdcpP=Hr{U|!Ef=ei>IHhr7J8*I;C|7kdS@&>ZvF)_)*jlcK-~F$G8p?R${djG*cgH z+*n8?;Q80u0@q@X9|f)g2-!P9#umg;#v5<{&M;)+|3QN9Oeuj)~nTV|mTJzv3!HFLBm{<&L@kV{p0yHUN|1Y;M0m%bAxH`dp13 zFRo)6+q~{2h32FLHx{7%mH2i9%&la!zlN<$62*bv{!)3@nKRJ1U^5Y`fkXtr%<l z?^&+frt$oLEdX(|M+wYsLeLgXiG){*KNvC{JKvVP{&t;8Y|gu+I{~he}G6 zQPjBf)_k4TSm|uq?(`->Df3Nox2aRT!Af`(n1BEMuySYG$7>H^vM|a<>;mhLp2D$f?O&kR3O;WdcwELI!^EqMha+MbFSUg&Qz=v=nN( z9bss;Zf8?kEsW#p_@G~gZU5yXZhjV@_#hX)<&rWy*mtd+Mz_EqGQ0ubcsO4u+-~A{ znZc_K+tB5e{7g-y9)p|DrnutpX;mDbFqTv#sXG3`7lN;q zpk3iC3xhAzAFs`E$aS-n`3;Zg10FWSxV*K)i$?zh@o|i9NHgZ+0cp_3s6H{_6`(m5 z;b#2o2jZ2iq)=K8N8S6(M`IVjcJPgRUY|7&v7Qg;npZwig{CL68#d-fNt*R6vo~2< zr`znHJtBPPis*!lKXcU5i@SU&~aM9l!M>mi6WSt;*ca{?gF zF}1)%=!Ae{Xbmm54Y`FH5L_|6mq+_-pnI--V3)-}?kH*8!A1pAwx6O zl>w=b*8t6tZ?oHX5!^`o9*Wb+<#jGyefdJKnO zaR5LCb>c!$35gr7T)1mo+2>^q*RxD3w-kL~U=W6Qfqql+ewi$;{(2C!UV#{ib~O{RJrgSPI3&UsbLtvhOFcT%miq zX&LW_It|S89lNg__TKWSY2a*68S6C-&@fc+cA3zd`<|u_!Gxb{3(@;B%c7&Q))_f* zs7Ca4;M48jh~a>J<_CE!n6J72-jlR2w?Lsq=-kqp55Vde@Tq>+G)62{^{7744bdkP z1-^?og1hlv; z@&A=(09`{T4kS-CqcBWx`9t&Ev-Q)eZ_1W*^E$|o#^;1DYpjJIyAD4ebg%-?=DS(amWfil$)1nC1}wA1H;euE3!+fii`-gU_Qku@089t)$b|s4UVcA za+bDqe%6&eE)7@QsoXZ;hI^xUVgRZ#s;f@fDSrREn5drgu~C2DqSS$RaYbf1|23G@ z$yuK7&X0zxzhY7OjD)>9{*~Vu_^*rnTGG6TQ6&z}{Ya?_daq@$6T=a1Hsh+-_Ja+S z9i*8X(TvlkNwhrk^m`^%aRB~77GfGP^`ysd$`sW>=6F{K@lnh~YvnYAL%;RL!|N3wDM9x~FrRAWDVbTY5^@DC_I7%u zO;!76NFMX#J>W9E+>-A4)eT-3VX=;y!zF-bJ?9DZ5VZ;`kvc%4(iTOU=od$M2;&HHy8{qT5+n? zbTQv%;PvA(H$3~{0I>NKL9uMcqkQ@@uKNpaj=!qK zuvrD4k-P3WJbqex(1?cO?bVuZY34q+5Q4QFPmG{Os2;eU#vZwyroL`y8H~vnn9Yjp zLL}r%O$MoRSMH!1bWv6k9t?6P{Xxc3%7yim_73T`AX%la-}6pcZ#il2&7V7LwvCQr zuc?iLG%P#B2sL3iV#wJllFy{N%=r*cv^L%rMx6%6K^iqSm3Iz(_#ib=WCJWthx~kl z3_>vvB2b`M|`w^Ik_fMkmP#J|=y4+$V z+}fwc@G#l7i7-#WLN?qAAgR1^=dAD`*9f=E@iuy&rQ!Rhk(OyC$1v6}1P|^m&9^2i za#A;8Ga;F(UIc7NcoX(b|EocfTHDAd@fGc2OIxH1HyFR~7=t}3<_5{WHH{erXD6=* zYL0kCv3b@2Q??DSm_qh4eu14jf(c(m^T9E<<*5qlRknqp5yt7zY{OLqV66j}W)Fc* z+jMnXFe$tW3m&6K-1r}hA5K=bJr$h3ZVz*JMZwKip5(d(b-We>46`mVQiP`vSHe~3 zczeR!?)A&XQ_qI0zgJVo1|1LKSS96>HW)%Qf}0} zSFANBA^(TOm#KeFn6#562maDs0{@jw-R1>)2>_9uw}(2x@{!1bIF@C!ee(J#24a13 z#|D#V?!UjqTF;0gR8S z61XsVkIiF(`rsB&VQqI9o+Wp0dkoRj)XE%wh{NN`86-LmkRSjF99L1@yg@~+vmhj$ z=QBSjgC~BOX*P6}=Gz{lYw;|}_PeDohKY~EdBg~S&RX*91BwMv!(Jbo&4DK8HyytN z@sn%AEG;|C+~Xz5i4D&=!+6yQxmqV`D@8B50D$p=--j*JW8IYy2%O&~unY^6-fb1< zvvix$;U>pCg@r`O>h#?7AyFUQDAI%|W9BX*Txa;Y!WYVLNVM?>%#*{V{@OekzmF`+ zd+Qk=Zo6IfpI7bxyqq7a)mDzy8tuBud0AzY-Xt7>ls4tLUEBq!UFf%jf zIV`mXaGzuJW8V77Xa3#Y_7(=Zi5|EwK*e+_`1t)lfeZu$GB2s!pg)2bFx|R#o?&aH zU_M{4ur=-n+-X8RhKUfpSF8SF$djQAgn8YnBGRujcl)~9XbXM)#^;o_=H2W4NrHsY zOcf|xX+9Ua#Ew_->aKKGV0W2n;TDovRtF?haCHe4Eha$y3m0_heJz>`8%X|KJ45N< z+4#CP;XsN#iPL3=h@jZw01rG>p3U{^A_1vFp+f!oArY`Q!DA@l=OQuuWlf*Dn`y?> zu`(bT0ffw%b7^-fq)u~XsX<6#r@Bj%yP}@JUS{d`1h6nOkuH*BQ1pIr7qaJ_Z=TOi zu6@{8B6HlL34FxsRb^YJuHScBe5mir&q9SA_h_3O$VC2TVCmThT2mE#dcv0sAHiOv zAEth+Lc~mB5mUFH49R$;YYBu{?n!gJ7&i;~dRQcWr&+OwfJTL-9o7x_!Rg%QzARV7 zds}0)n9BGs?=~?%_yxts%KiN9H1bRMRJh1FO+Edc*V(M<5a`*r4n?gl}Puj z2R(DP6iD-Fzb=IRx^d;A@#Km9wI7P}_!CrOM*KlDZeDH0Z^6%U#hb;k^8{VtcC!QJ zIZS;DnKadzsbF=@?$#S@R9NA))MBU~@NRg#bmIDer@6Z+g-P=OX{rR*S*sZ^F?|9L zVldHPaUFE*Y`9;T5h0#C;JvbZRjz1uO^gC<#rW!9#}1>G|QFC>)plm&6N z;<%`G6hMxZ71FLiNI@p($+|LN8DHUNKgGBWYe&z78j-$ygOipLV0_WQ8jgJZa8YY~ z?X8B76L0fz6;i9xQcvy{8xw&wp+~ArYh}S|0yS}tiQNCzwn+g7R{(4ea`8XGivpnhZ@2!d6)InR`I=Q#Xgvp|f~ zetpT)`g*nIyx;nd#JpZgu<0XfBfh1rn}uO7ud`i_Czl6v5GWi;3>_;r?epFIB% zt75}d&Tk@=+<3x4xA3kr==2tW*}Xe0L(=kYap$f(U>C)OX*ajwW*Q>3ZCZ@NA?CDR zwsQpq@sx~Rq5`=z@cmU!)=lUg#8Z$5`is*``(q|mv>}RNCuBaOU{9Om5)}dmMsNvtLN}5kATEW-|yNfkhsz&!^QFE@19)sFUM* zXGrXz>N!jXF|})yc9JXaxL042-T5H4@K#T>g>YaR(~Sf%2`lvEf7-`y@XyZzePNd* zpPfelsR7WcD@EC$ggUm_eTIMiE&d0f#DRX=fXGmnseiz!`c(=`Cgi3V@PB)!|AMQ| z@D8~XL&KZSxo$~HyM#CghV&6Fid=is!)A_K7C;w#FTE^$se(zws zuz~+rD#xxRbE?;qy1o{YZwt%Cksn2ck1rikUPuEO=>$fc@@D$pWnT1y@JqcSRr15f z2a__G$<4$JgX|zv3U4fhGcCnr{~j6+(E$ct+t?=uUVh_A>)j`3DF-MiqTA*E57))u zyg$jYREcFxmbRuG&Zupk5PTN7%BiNPahVM2__OaCcX@G~IIB}H`AzHSY8)73Vxbu~0TwCqv#pMj}ocgc8Y<(~rA0{xvlT%%R<+X=6rr^+2P_iZr_KrmzI5WmvM1F*;ZZndHK7`Bp4Fk-@Jv z34;FcgEsM9?20`NeI;UO8>&o z1RU@T*^of3e}7q#rMi@06mp=-*PCUOPjzvG33ekOH`4MxUFtL5?Pk8`zIiUtvmqL) zY7mCbulF+I&Xmw(B=^>IS2l!E#v1mEA&*c0cc9EFGGRoSgzP=;Sk_nxXo&q+Y^;e4HOxv9&`;N8<7 zs?4QX0hgpfx8*@Ls()qOoB^+ZlT$sdUr0);r{o`ZaX`}dj$EK2{g#|S%tOxOrxf2~ zVbmD9>-}b=89TuZfRyA5J3!=141M=Y*k1|QOsE_{wIrO-igR{Esmltt!v=*k*MzRk z6(24*JN9zzR!0{553;;}*CX!Xv-GTU+Wkj@ok52fxh+wLPwnIS{CaE1ZA7+_Iy5HW z&CtAO?`IrAJGd{&;$Ef=1J<_7E~UfOW}DB45v;;Uoig(2C)bIpKl6wA3dW^_wu)Ud zQ+XEy;?6*QpL#3!oY2`O$QitzZnl0g>(SqnmK)lRKr#T}_;=NXhcfA}kU(<-P?v0j zIvoG?Vp2GV+)1DpnG@{$dXVHjLm>L64bGv1VLnN&*Huizt}{XJVwE}{N|C%m7m0#V z35`%K`Mw&1%eZ+xA9gd&Ub)42ny#*zs((WXNG>+OvLIK~ZeCt* zXNM}dMa%v!Av*m1SmkAEV4IdW%{r^*(~!u5B;DI$OXIz!qc`nn=3^bk@){j9a`d14dhrl16~X%pIz^XaBA~4dP3wg^G<~K@#~RCpMUR4 z7b{8TLSV}*)OVQz+Ej(Z)$(Dl{)^81AK2MB3tEcWy;F|sp8N$e83pI)BC+JpN=uiT zUb$&+bX@#BJr$8NmPbOB;|4XA2 z04W0zfG~gAS7T>{^RJF{7I5ok{yM#&Ual`j)2F5q(v~8wtRDP@~FV9PGw>65`7=9 z@NLznUN3Fwy-ldXLIWnukBDX*K)H6X`xk=lCf~1E;$`g{>J>IEmYot)q>h~)f*tm~6a}YMXvF{0 zs$?;^mGs$@)28i?)djL@%$>32SHwj%hjxhjvXW%$Y{H6`Y#6B{Zddk!3KQ&N{q$g7usroGcnwBdhg$+Oqt!L1pH1KH*@%yv>R*14>>sNwR2UE|fsC(ef6f{C&KFJPL&3<1r25^;L-MwfRDVGmO#&#=z(E7c#=?kyUT|T)R{MbeSkp9*cQQY&DpEu7rTb~1+SL3kgqnx8KCX|9#Gy7PA*VG5T$kds)ElQw2lX%;b@5=8JY=zmuZd<1(5z>x^pjmxHR<-R6* z`l@g@)g4~N5tw7TB{po@nn0;9LLyP~L43SoTC>ye><*cC+ZAc|t8ZQm@n_1h#t(vv zxQIckUw;@DuCifD+-O-v@ky5P!6hlisme3ZInRi+Vv3-R9@QrP8Xr_vW=5UE?~lQ- zpDgV&!&bt;%>Xa{mcx+z1fXUj^JH90uc#Qk2OQNF4JS>wuL30G>ONJ^e~acFg&gkke2Qa1v0+RC?k|;i8h_ zL&|BfGOsy^j6s&K$$VK?wr!&d$zELiG;u$%`MaDbP;Q}!ter7)GtFCsN}?vI06%+Y zHuL)kJ7l86FeRoUFJxy(c^XHxZC^=w4S1i&$=fsjt6}^mR*8SNm>fQwf0`C1YRpG* z;yVf!CTzqSa|}2Qpv&a{GXHKDt0YgBqT&(vWN-osjIXR1K5%9|elSLx$-NXb`((-S z0rCPV<71`~jBhn6Bh2RxM%gvtyYyq9|1K9DBzxm-JLioJ4Kni5c~8%c;vBKmaWGPX zR7?oiYig_Lq6*HyhUGMc9eQe`>gi`$i^67Oe}b}$UZ4N^%~MXfN4{5yKe~iu1^crQ z34MLMw0Vq&4JrF!)P8Ha=lxKNpMP7Kd6U-ig)g!V^xdYY%U4GUp2czP!j+UH319)M z&Kuxc(b;PaK{VL;Q%^Z7a$()y+_XvJ z;z{u-SNT2(_inq&t(cVBoIyFM?Hf7}lCGa(R5>QYey;p9ktLQ$2HjGpc4Jsj$Ldni z5nZ1t^f?DT(rRS+u`4CZwe-LtPvNqlQ*Afqmg(Xn+ewxgq8BR$oQLzcor8WCKRP`d zLztXD%^dxGaDGSxO;=9e+7?GBlnxBY>I z9NV_B@cNq14S^9)%~UA-uCg{NSke7X2iu*}Komn~4OQ<}uH>uNtzx$rDJXA~hmy@+ zy<$LVWtBM`B0Ce9y(*Ly>|{Z;-O`ee*gdVJc`I-Gac%pX+eyL<`oQ?l;u84@h$Xf7 z*VS5M#h@jFXZ?r1QG-|kKr|vN>XaHo|LW>so|8Z4i!6wiCNX8y$d(OS+|Kxt|IWQv zY`$R8Wh%f6T__Z8%I+-@QOkfNJYUMlZ~gP^2ka=EAl#x_1%r)a zi7R6y7Y>;Y1XZ+rpXuIt=_AYG?cG}~5Mr@#-%;*?bv=WI6k;2L_lT?$eHcG$E5?3H_3Ea z9Bao@&R1EBU#y&Sdb>D7613Kny0;MIkwisLFIYugGocyuYzk1G`ESBD^x*ucZbDr+ zDw&X`*0jXbB-h064`m*86M^zTlRzJdq(crK!M+b?$Q zhk9Fow6_8Io>42*O7-IgzwMs0M`C68PGx5J3B!X1a#`dr;?7}OM8v@gmGG=1o%d0Xc9C)*wtS7?63TAkp?L3d}UYbZIR zLjg7VQ9k)cZ{7}9J1irELz0E~_j}*ghEME2c!%Bz4)iqgd*?kR4{iMze2*yl8(D2T z&mQ^qy$dgSg1ac#?G+ttE;rO@cY1gbKeSwFXS(!^xZkwLA=sxM_t}hPA?x1z0M7o4 zDg35)M_bVWLIpz~;+0Ol9{nYSDYpjHK;XiY`89jrU5KPo|J~oQHFt!E4DV(UZ`HR_ z$!T~ix+TenUZ9=7Z*tZ7xm+wYhLpbU{?H_E2!J87%8G*D_yhHK0Ds57)-dK zsg_wb@`^d+vZ?pOvVPH^2}gN=)82mhM^S~+z21J zL5%L7yWLOW%_5+$E+%J(3u|esx;@bpxCYy>&4zNHiZ6Yx1xi6-)G=f?C~cZ~ieL!HN}`k%<&i>5;lP-$ykEsnE;vyPdudYU`6-~*hwqs*qUBXq$t^PoBt z$k^6nayr;IgiJw(>Z>%_v?knm5EF#ccSSx z1(&^zT72sn?^-k&xgEJQYYvxqY)iV-jT@MTPL!LQikgYt%lnh|a3TmCb*U{Nh)@ty zh4AVsSiL9v4*3AdYGFfM={5xpGTCw6!>YJ|mL-79=>D|wzupx1;)=XgEEUFX;KV55 z>ZNX|!a73)#n?DcAa=d5LC>mo-oy2}L>L$MK!#j{3hIj%gM)49vcy_wuKu>UBB}|m zG|arp&eS1hk#Xw*)Z6@)n$hD+QQ;eG_|S{O@lSepgXW4YJ(DWj zgf!J{2WRb8T9wPxEXJR84jkJxK3T#|6#5+oxDgJd`YG7mcAx2f`)fHSwT@ey zMhzC%7%bcZzD|A>dz&G!Th-g}1RB`jE#tmj;1M-fhHk)q2_2F37||aNHns0Md2Ei` zxM}%>@Js{i&9wKsS9ifXl`Kb`s*csUUUj*msnongib@v$>YGuSNsa0?DRbxf9BM;S zQc2zTJ5Tc*y4cQcgt@%+%OUxPnEXF5b*o%J2LZwXTvfG+T?Hp8lRP3#_)<;LdC^dS z1bVX#(82RUlC}dwTG>-2?LT`v4_O1i7Fkgjt@x6;q6Khr;C#Mz8?#!I6x7m;Gfqhb7!j?YQr@t?)%?g3(!Tp+GdCXqo z!j-bdL0^X=og%n+ zhIAnx9k1&&Rab0}N1KML3s_op<|~nd&#sMnj4~av>_`NDtKrj;>4RaX4X@?FsJo3@->l4 zg}DGCqxmZgc@Mhb({JLQ*Wx3_2AB8ge^n4Jn(KYmj8nz?pOPpfo>e~nYU5bbxHn;% zH@CS_qKIP2J8%?u1g@4+{9|F{kNSo@%rSQ z)~U_KkWfVRi(4Na&K+pw1d?MJ<}3CNa$aP>4BWmSp3y;Hqw3afEJo%X*n$6x)!?n;4mE7>O&ruv0x1zCK4;2_kWZ^ zTdwb_Rt#@g+;*7UQW=K8$FS)z2NAx>1t+oLBTFGVs8^3#j=Q5nr^IVC3WBbyIpch( zrT>}lc+!vcj^y(?3qR5zuxfot`rwz?PPO`liJtY(vOU{*bYB;*rVbVtW|Vr&ZVO0y zo^lWK>3uy*NPVd(lWZi2&GbD9wMQo$-wpp6d&ZBLsi9IpZyVDu^RDAxM$Y@detEsl zmx(zs7Hw8qTk(0mT_0ZN^y-Z(qUyCa&uUPa`DwNK zB1nx$fb}5H3%_LoQIlDGyQ=(BQ* z=FUdMng3|F%UuF$M8tI@!wfy((BXsgrN-oZwNCSzUpebdFne7gp)@`Y@9n#h@RQGR zcyj>!Wh;C2%7Q+ zzBVA04{PT~h08vCEV8r+t2XK>%=E}X%HZa>8fm->Iqq}q zDUQ755srFfu+S~OIBVJEg%1?^e!76?piRey+QL$6v}IpdX57JE-k79+;2A32ixfIl zm*5yt5GgR6la|}kgpBLDbra+jGuiGNxwR*8S*|984TCCv9XouJcj1O z%3I%xDwsBU8{A{7nm=AmpUD(LI6UPKUGSZ~oP&bi)5C1hm@9H{6s*~8rhwIsmgH%! zHXj^>C~8S#ujycQT|(eHw|`bgoTiMM$nMm6JjW%bYn?C75o^cc5Dup~6B5)>00M`x=#2$12Sxv__m z-=Tg#5>o#ZxKZhCL1$l-Y{DCZiV3({cef9Hu4U87>oY>$SF2OQoJHT<$)_1IxjDKw zWn6u>^Vbjz=3amHcPeRtg1(jlQET^)Wmo*izyWyaxt#<5sNLS>_+J};evVmD zT@i%>v6f%W27WggqZPF5p+U&+p+ce8Pl~XGQ!+n6A4jNnJZ4`DZljv_3+&+~g4fwz z;E=#|PSkvsBc6FkedBzERc!siQQiW4XdMw*@RE%Y>Q?1x*d`xEM~MorQV=yBL_Tiq>I6bh<4!T9EH^C9q7$1Q*BeccHDOA zbr)BrUMNgnU)K!xV8BH0{~`xqYeraO_#`(h?grvjad3SDNFZtYg zQDHHu;?*mFkv1F2Z;oSt<)1`>>to}(B2#ZSV6Vq3p!>9Hoo3*t3D~?Auj+!Fve_DX zsKakR#gfS}>p3>hy?!Y6 zKxav2RqFdCykS0`ljs)tZSG=tY1ed46pgQ(+gxMM-a-f})+OBb_*A_7Cp_PGg2}rj zl@%Hnr>s7*nGGWd__c=;u1^8yo4q7g@*V`D#2{R7Vbw0Xhw3^)Qqe`2hs z4uL{1Q7Ev2(Ym$t61uY+01t zttlpZ2T?5$=(YH<=T}Gt)?z7@+)QBG()XbuCNl)tKt=T+(6U}mWL5E-f4pMK5!7kS z=nEL>4-UVzZFpfMsHb`;C6z}2E+?Wdd!~iRthQ>23(*{j*lYRdG2!7QFo0xm z2zFmz>#0|qtx4bpL=7W7g@sr)5ma2rxkP3#f6EyKK zIT?ll3Vo(AEQM+?a2sb}X3Tgqv!@IDe`I}kIMwg}zY)qhglvi;$tK%TW>F-e?99mC z<2WRHC9+q?-pZcGC?tFDV}z5_`Q06zu(WhKELa7Ip+_Tb9=e(=lvXy;k&6j zk|t3aE|`rAK{DH9aACJg6O>E!^udqu0ab|`1E&wftw5~mjJ6jNuLsUe-6o!oF0SWs zD{H;>{D5>DOH_O8r%EJ>)b{hECv$w2)v(TDyqDy0p+qA#C}4w#}qLuz({ni5vo{5AswJ`Y-=hrUGP zm&4fa2sq5u(mY--rxAFWyBB<1E~D;qks!zb(?a1pXr6 z>RCq|(&K!_kMmLE*o@3zyX;=PYtJB%5^D}`b%5Qtrk-Kq0Rt)rNcPW-jVQ3?Vf;*T z-%7GT-Sh+Mdc>zsMbO!{p>xJ-6%5Td48k<=FLPVgWPlnS#TZ}@@Z;J@r6|!7fxpcW z;6JsbfA=O>F>v=-)qk73zx6K~NwB3KZ8z0Mc*yV?Xt)?-WWn7Ky#>+!B*iD&N0rv2 z_|A9nFwWKj=AY?)rgzsicoptocjtVCw)3Y-7p57-$G}Uo>3QaWnE? zB|fk$VUYe2<4(1R*RUN~TT*NX9`oq5bjB%)y1{Ux*>XgbFMEEQXyoZo9Nhve%i5NL zf+9b|^x=;*C#{=@&f0k`QIi3@p7Mgj2ll(}If;HI`KjY?8PbBly9UP4p`bpuY}MvS zV*ww5fET6Az4>k)3pQE#sgt~gn=SU!7IUR#qwJR+Uo=*t{eK>iDI?N85#ooR&O*O( z*FS>P!fwI55IKO`oR#6D(__563N#}(6+p#neK<^bmgu&V=39SsI5e^Mt1$EK4tHVe z?U&Wo#v@~~GA1=WR>=_@ue8Rr9u|93)V^+WF%;fEKp!-N^? z(e+{*txQak;W&NpkES|CoMVKm^mcD`{(0xSWaTZV{0)#;W6f5r4*a?C08RGT`6WI{ zr~DyzCTXjZ#M0+ur$~F3k2_21c}Gn?TMP>k`|FzW@JzvBN*PZsvt?24*7wB1YwZ~$_S3ACo2Jd*= zvD_nnYNo@CM2usZ>{DOQE$+3Q&G4H|o?s1Xj@GjqBQBg}wCv}-0x*e4y@SknV zC4>umDv5|vu%GI>XE>GgrPVkJ`td>JXCW08yZwWnrZH0H6A62F|2M6bmV59_&)J9m z@Cp(vv&R?hR<^j^$V{y+W+)B2%=I83~Ga1$L}yh0B1b293g%F z-<$jv<3Hp*)?K)e@1Jv_a6psiccH3!FLN?_U(IP#z8lzNp)T7slHXpTntUsLOkU+D z!e3Syp!Zj2BNYz5iow7_sr<8hn^8c)Iv)MgzOs#Xjo(qP`G77|RU}QtG zc(BZ=KrQqQvP%b!9@AGIQqZ6Bo0pyf+}NUCMp83-$05(0?08tTRJ3@-QB%8jjW;~! z9ECg~J6RZFqt>|p(*n5lL)^_T4(BMp`?=!zgw6n<#95f60jCy_5W^-zGj0Qf zlCp2_ZInmG4GRzUvwM@87F4nq5s6uFE0py^I5FV+VLN0<7FqWue~TF(XuY4!0cSgMKe&RGO-MYr9Y)PAE@o0#v1^2! zsw}J-%0h)8e+f(kOb?suEW*-uDnAVt7IFpb&Ea#zbt@SjOb;j_kRi7}_S= zt0^&lq(_YCW&Io8BQ;KsMBCDP*fzs2CxFteZv8@JueD(_ zvS!A8N*aMK6XVW=y?X~nE1Rs)`hrhV+qolpPrM z{$!oCRU!UUk31Hi1!2xR$uCkLVyqyuM57MTxadP_S>#!xi9D`346pkK5Fsc=2{;8L zzFV2VulWk}0|UP2|K@H4eE0aj7wEr3y?sC0X-i_r{8_3^v6>vid;qCTQek~pYl)b( zX0k#Ii$vtknLSmnv+;xyF}r1(W5W@6w7hp0(j#A1tQ$XWI=^N&;_I;)$uWke8QH3Q z3bx>lO*~|+Y~`M9-RpuSn2cjfRP6KQ922dR{Ej9m&vnjP&v?`}TRXj|8jQE?p4S^v zj?V5y^80Gws%W*MCX0%h8?y9)&r?^E5|YbvII zZiY|Py3V4TY3AOlmVIbI7$?RFYD~#<&Y`WWSzsxO&O%}>uvAOXsy3M4pq0J97 z;xkp8dz|disP1=j!tr$FmQLHxtF2E={7)tLri!N&Ylb}1iXA;&pYaKODcs}E1v_r) z7FX)Ln9_gv=$4Q#?EQ?^taoK=iL5Pl!8yU84RuTg2S zaPK=CQ(+aKenj8Wd8YjNVR}nLv;y8_FfD&-CXzws$({UEKlgcv7bH9P{Gca$Nt<4l zz;1jPRrf=%kNZdXpC*VwF{;2RkWY;KC$aYe+|_V%UB4MjLKlp$axOGj-0f+Dqk z4`RRo8T(=LZV5AwW!|GG7H0qLONu^8@-b|(B#*X+=5C-;i>EYg3d;EE@)pKkCXUjx z?kMxBy0q7t2B8Y^sGyCazs5w8jMcy&by;Z5Rj!mvyaY*O~Gun$Y6o zi#1<@=SAb&LBWg{E56_1c>_WYsfe0^%P6Hz7Utng=P#@|*Ya>}QYZ9062bNe(5*W& zL##JL9#-?;&_1l}UDvlI4z^dg7Ou(9lBa*g*w{xVe}g|W8-hpCd4>}9PI8_! z9vohkF_W=k-jQpqXy?6jaxdUu;K22r2gB@>#J7)B2WJx=qF6=OWIkC22>b$>GzjmP z(ma&CeP=xLN{Hv;Axa{dicQM2A$C|Oe%x<%ETp?oeJbIuc5S`>^r!cXQiLtBQK*_b zS2)NC_6q!asP+)bIvdRMERr|BQXG!J(q5MG_q8A#{EhT|`WT=RRd|0tXx=kVlAdXs zMb3NiqJ3(d1Bo2oSOw8D;PQ7!DkPz3`yeWM`jMZ%80dm=|DYy;Cy*cg6$`0;$3m6s zlxTr=o1Dr$aSFfz48UnNUjGeZM+je#jQyb!@rH>g-6#hj(kNK5FsoW7YLgb6`~U2$ zA=L^FU6`#+V=gs%A!choX>0Ai*NS-?MFs19)8V5Y&3#}))iIdmnpwq8aKpuc?}?=< zU6KoBs>9sodvf-1k=>3uWs!pTr+H6AKzY~7Grs59k%;y<6Sy^er=SQTd8ysq`N<&V zj@_r_G8mDgrijt>FZ113{m}blVqQs~!s8dtyRHa#nK_}fO0lPb;Poi0CEvjGC5|mr zS1z~A*XfSCFIwsqFYddjNcE&#mm#h72>HVCZc)84%(F&ODVTj)u8dih!i>SU@n|^< z_o4MF7L+8Vwg0}Hc9G!C!e57whQvK7BvD2*Xi^)?WuRwW42T?Xwcu=)NNm(Y0j1 zPM7>V^?4PE0YMry-EhP$V^x||#fHnwrs=k`S$8;QX{STmzX~xW&9Ico1v)>d-f>In zJh6*-{SD*BJ9&h`b^4E(<(#9tPQ%@+V(;C>F26pwAr_rJ`6Gh3U`s+w-Z{|vrGNIe zYaP3<&79?<4i*Z~lzF#mtSjN&BgOTp84EJ4-lAkz?~U%GK6-Hn+S1Vu5=uU39MsLw_bq#Z$ybsm^ zSuSMA6X}s+yU9}6c@=wp5lQ}rjs7c~8uU1-y?_xJETm8c+m=`lE|Z)yvr5m-R^=f! zn2nF;d)GQd09%ZqKgluSA65BuUsiR#7{ap@1|3s3TR)dZ+4}3x9u;w4;&9&DGY1_> zwYxs0wxhat5&dFa{jrC9{Bxh`r73FvFr(u(sF8HCseK&lpjRCAs$aKMvhugQ#@kzm*|IhjoXZ4qMMz?Zyv=rOPy3n<0%mu@{c8Vg`qCYu5tr% z%sQLvOzdA;t}$|ZMwAb)cVZ2Drva{D|&= z4zm;Rs%pq8ETBqF`ia{qe`)sUY|lVp%hnHj$eO$mmizxO*1y5lK{~*U*OXj+%j#9o z+c;A$W(cSD2{k}5IuIKkT3@cr-k$7d6mHxe7IMXX>Ub>(wK{tC(oZsmO7Cvvkwev$ zQIUW9wSbDiSAc=VIb^+Ba}vmPb^nAM(eYU9@bTNNy{x7<_U@{;o&2g%4Ix4L?urog z?`NTAfy7VG+1T$W@hskf!LJf%6q+09$C30r?n(E%-qs3B#QCnQ@9YR)Ncq+D6l3b! z0#?p17LTClBiy3Dw89@B-A)RyY@oqr0>NV$LGR)+{K+KsZgP3y0A<=LHYVcdVM62j zHN^0&1|L7!GVVc+9hXg9d1bcL1>6hA34y>RO=JIr`B&WsVh7^7fyVMU84YrU_?DB+ zj=Diuv{zLy@ol!QcJYE4@SUNfN0z$>`tXqhk*svvlUDB?=m)~5PNP+eWD-&ccp zVhN&Iw?9+gW_e32zmHk$x_hNZZ+WMak=Yir+Wu6VT`1+71IguMx&OfK|MyMM|E-NO zz9?Ps^850|xfilTyEK=>0%v3a8{^CWZBwX71hoa4_59dhF;~#sV^3a<$rC}g06&u+ zrHzAT%tEAs_S#@_~h>`f7q{1ZY!4~_Q zGS`$MD&%_e=YkHGKMy1D9xnxSMfK5cFJ=|r2L()~QcfEIkToy`9mSs!3wRGz{AWE6 zsc1_dkCRl~P|8*;VJDpwQB&poA>=?nDT}5^b?K+;vS_M^wjs!Y5~1oe1Avh_uD_C# zY=9Z;`|fo^3{l5jPkhHKZuWN(-O-Ho?M!ZZ|3x``PXKHbvWUnMh3jZSGh3S+Q&)0g z>Y?w0Lcl5a{p4oyU&~rk0s37&@ptpHMaj~PecH^*jd>6sKLZ z+P_^nVAU7DHAkJ661IsoC>e?Lb-&uZxty}Vy=(5@i?lJc(b-XtKl9W--nxxDuVz;f z!%jN>pj>UR3Ojg^Q%RcIF6DNUV+8DXQq@H;(IIr_UebC>g}rqKmfNsMAY=#po-p9p9|X9B?8F`aN3W@BC*n6oh#CU@K~a_ zK=+)fDWU&bH3`o@t7hXgc-1gXKu0P<4vhy8zA-6L+ceUK{3nD??<{N;$#mabk4dd4 zwa$UJop4{59Q83qoPPOZ)CGjjMB?8AU4qZzf;48`q)8%AhKD4{z}I>kyeHGSolIPa zRQ*NbA0GV3aI0kG55cMrCR360b6XmzXkudOh*vNih~-`*DXX8C!2$BpgT#Yyc-!(| zc33z>xP}<4FzeP&Z2f1VN_cge{`b|UZX;l$cvTF9Mhz&?^fA=G^X5(d#A&Pakr`Q; z{zmF)#VJO{DL}z0Q;qp-)fY46m9yL(H%+D_yT6*daC%?jPrwjxJTLz~p8M6|irrdc zWDgowu6>p?J*Mz>v|Q360y+2Kga& zS9t*F>v=fV;G4hs(*G_HxF5ggfuF|Hu`A7I3#F{dIOXp!ZZFY9l0MV5@W0&;upTb| z{V%>J1?*E+a+k}_^90vuZtQ!MU*SZY9;-|943c;CkeJb`t?32@Ei?qQrh0VJkdtYH zg{D)j*;37o!1k(M4HBpnJA-efdzXLEG@MboE8JD?xOHzh4h0f&`61u|wn4(B$?@i7 zDoT`!{UCBT_o3%|vtuW)b}Mwk6-%^+H0b0g^7Pa<5!dom>)*WccP2xw4(JPn%J8`a z1(Q%--ap@uZ&I$AG^8*5nmIH8pR{hI*au6i8^MoRYu?b~U#jg>nT!Dp1xa=&xAcOP6{<#2{4 z?p|*JWj4Zhjl}+z6wru4IM$c=Dim?|$;2oOj$C%5bz71D?opfVt=I zR4FJ1=&|FStaet?mMY3PPl5rj>>%^+K@`{qxo@yRNXr~-EplZzp8kBH{SS1hHEa6NH0P5=2k z8|5&!?A617+<;-|0_yElzjr)|mFCah^LLcO=XOhJ)LP)7-B9J)Y^SO(<|0k?8r}1w z)Mrb)y98s(%^v6p+fdSrMPc+*s6n&ykUU#XE(N+P5KE%9{_D^#`>sPMJDp2c2SjSf zc1V0WUS|Ki^~Fz_t_A}qBn``h$G@oO8I5Ob&=eQ&(pMr{B27GzDUG-)B@M zmvE(-0?$SMZ7?zYn{QbuG}*i1hxwir)b?J*h@MrEMQsV~Nr|3f?^P9?BN%?EO&#h9 zY5E~9J~w$~2s~Dy79OkwVd{~krs}A1|Bb!qiO~BiS)tJa!kk>XsnZpgXpnPRdUL&u zKEX$3OMt1QDUh1rpVnt8N#FX~=J@=XF&4kZ-&6cS{_1JGkHxit>mZOsU zku#`+b1_d}`av?5GC%7@>oCuGKia+-vQ!_KY3!E7m%!~)8$Thz8G-=gRF9PV`%zmn zaw@z`%&#&T+7L1^27hY^u!aHw@o0@@B`QS=EvSeJQa!KQj02DN^QGBqZcZ@7uR;x4&@( zd}$~tQU`sw#upk$+X@uH2jT4777Rfd%Qw6I~f4Grf{5aKPvt=GbyM^Q|7F82Dinq&zvJgufsh?Ok^e`!(H$`6dvX5)% z+Uw3;pp8BeAtMO861L3E{y^h9;h<#Jos<`asW@NE`}jduY!p!y9@fWhO?)|G%PfD# zfMf8PiOKQT;aEp=cI(imA8u%e-J-|m6bYrPB7`OpLj6HkP(zx!GS+R{V${2-1o1O4 zc=kQY)!A10zZhXRs|bOVq5d~*?g7CwJLGPHyuyAMUeBEP`aZyA3Z&EuZC+)*Qbs(z z9Sp2Cw!tDNo9YSSG8X8hgCD_vX^?HW=LZh^0d(-_Gh= zFlh25hn-9y%=k@#@Stf?n1!wxSCX67OS3E0mbaKnU<%`0`jxVbqpyvfM2okK?aBGK z?R%-U-d3*&&yXtK1T+kXU;E9n7X;1N$RVE=g?*9?=MRguqmk;0t5qX{ObKbqMz2zp zb?95Lp*8MIDfgaK$mYg5##4GAH^@2(W>~5$d_7@rp=<`S^JIG!^4=groD!94Z%O;$ zHA|#egp<_dme@xffxfXVF@$Hv&(s$zh`8zVgZE#{I`RnMo*|*JbzD$f7#*t~o5v{y z{1@y^v-85c5Oe=(Vr?MfAgj~74kRMM*~&}|yRG+QMF)c=yJERb<6I3m?-c!Y3bo0B z>xN3_vSPUr7Br1d06!oi#lF2NC1+z+eN5?O?~HpOXB$w(pg`~PzLb*~l5yo3Fn?e?>ecnxg%nwxM>&$ub#Ho83eSEF^@b=(<>F`GJ(|ZAOM-_C6nFbr@q7IQYm>mF%MM0NM3&DN# zQfWpA>$WuqiS=N{e5;U=!Dn#CIJnw%_!eCv5J3qOxYVRl}OQ`+y5fuP|9wxug zg+zm58x**5l|`qkwBeqOypjFpL30i7>nQPWeX8>~@Hf5vZaZYTS!Kih##{U)lGV=MHmg$n|;<}nldf-kOV8#+9!Rb!g+ zq|U3d__p0e&D8AgEMG|Bkx}#b_Uj>n4!N8Wp^W)MGvu`|Z+?l27}X$IpPnz=-|PG6 zfumx1!~MM0ZsO{|&=j)`-qD-@{TF@Xy<*}&%JHtxg@>mk$%uCP4OO`7XiQY7L<``a zmeiv+UQGg2Yx0=i9XC_Iv#OXsR9kZ1T)Nk&2wm?_{nEMMIrmr`uK3KYw?D)$OdvPZ z&m#UnNmcM;-;<{S$1`Pj;rTr&nN1W|EPjadjDo{Uj~#|+1hiC5|YDKnP)KxU2YU4C~=JU*96Ar5cq94`!!=mlOwZFBTkTAOz4T3YZ zLB-0Rn~eD@6e?_agtMZ{59gQ($$RClfgMe@!6#;+NDvr*(|5n)g8rMvI)`^ZF?Z*K zt%I_le)-w>kcVM;E}C=aRgVld=9FEshYo%)QPaxgdRgx3 zG1A93;J%UXA^&iWg(fxIfYH?Wo>>siS9yeGVdSt9cT5}gt9m`MLV~RL-7eehqB7>~ z8hP%enrE?e&3B5|V7Y3WXJ!uZgBFipGJdGY!;N_^;tvaVzT*3`T9x;jHfTpgEV?5m z%k%v|@U7J?p?q|p+b%s(v1|a+Z^PK@+BXX1Th`6i75)1kj>9fP_4O)@ZWTzysi-V} z)qrpSl-?8bo>30J?HfDzy)@2bt3OIs!mB5@fW%srn6vkQq*Ley4(sCCOGm^H9QGJs zxSHolyKHBkQ>pkH`%a2ICjA7&&HsEQQUsB6BEz1)eB^))OS=IGS+_oTHc6=C6karJ zd!(+;T^&mK3;l~T(kfRqW;hp^QMnVmq3^B#iuOWD&_QhzxW6I#O6H=Tq@711j}22k zS)Jg|4#fdhMspal;UTv&0-`jvuybudDwYNHetar^YDG3>sv;@ZvTd;|L%hD%)*^}I zSM4l!aavu!HPUgBA^5bE3Tu{S5BYP9{ja?wxhjn0Emn&(-Z2kO}WIaB5&QX6HrOhp%ncSie80@|G2dkUQhL4MtaM`e#*e z%8KdK5$6sEKyC(&>br*fp|gElS`zJb zCtOM@CfNDD{mg5-jTi(y3739AyG0?ZNbYfNc`IFQ#qZa_ujm=rj$p$J_wnh^Fb@d; zOZV}n!@{o`6T~lxlE)zVN84u$xFV#&+3R&fn-_-x!&^r`6Aab;P}giC>l`OE#fKKJ zCwn#P_3`VMAY{ol{X3JWUgJJ88fgt$PL>VjS$`v!%T=PPL$rNj8=r$^**<;5e%G|Z zXnhd(U~6ZOQ1r|S>KWefm*QIc#cg08T@w}oW*?PMVgN(v40L$=E@**wSLp}-?5Ex1 z`Mqr{_~mw=;%q~$djXGSF>k^otAO}G>LaNtPs`y4wIoz~tnfz-6$cp4; zyKkW>OgvPu~d)<~)#VrD#zj027FR1-}S@UNb&Ou#UYrbs8{;^IexNMwo}aggI&YJgs>)Wde)3 zW>0$hjyo>t@QkyCU*;EFD8fn*wszci%S3>q;~qNCms{|}Dp!Oe;|-QKkiKIS>u<{jLB1k4~r zreX*PO?=}lN8X~zS`Rbpr{zGN3a+jZy&8F~hfJ~v#Nb(lXNFMG(0NvRauplGqbBpd zqFxUS(K(Df5(_&`n(<%x-jk0X;T2Dar9>HyExyOLwTgGK417@v-bj@$<=^7vxf@5AU8hx=D(=6v>1Tm#Bk9_tTB70ljS z8|lyOO#x^XmSRpZJ7AB&IF;kTPrYs%jcbU_s&P)~o?Y1FAkT%+S!;yE7u;5I+@DYY zuDsFu=nnAQ^HNvihhh>A+s{;^VwvS1xQj_>-0bS2mPlB*C@{=}VBZWBEnc+abns{# zNLkCNSe9C~^*l1M-XSN|$4x)u{v_r813<-Ce&zOO*7LmB3k>dpXRVN9%ty`}!LAd4 zCz0o@Y{g|IR>BYNk8EU(1b_u`kNSACie68ExLEj}LKC$m8go%t;ntaQWfc0O1QB)bHCAZk2ljShLR#jX zwYiEtewkO(O0dc{kr(dA$)-_RK6+jwpOiww@sLVim~r#$IeD!=CdV+p?Mv&$VZQG9 ztGzk*c!lO0@!A~OLm2|s3O&Y8qpSPR^dxGz1K4lGk7H;(HS_fRc}dmSDr9LLy^+#< zdyVzhU*ktAEiq`u@~3C94lkn?o)#kI&o~3>wEMsecMw~ZV_P)1u9H{K&vp6~FUGXM z&*61lL<^t1ky+49e83s*x#l&&aUTfoh|8U}{Aq5J?F{F&w6)N`P#zb63ITdYS!{TU z;A2pmW2Oy&Z3S;#5RhVDNE!S&M=6@VbSEH(Q}ivl=)LPbWCx zu+=(guSvy=`Yu+77}>l#c?!Yo)W7oD|5O+m%UY@Bhj5%L(J^^m#EV9RW!_o~BL(v` zO$nyC4Jd_9d(xK~l%&qVSmvS@tZVN?6$I!As<9QP9<~LCJ;G%*U#QSC&-WcuzE#PF zavj;!*#TsrcdudTQ;VA7jSswp*?gwK`Tmvi_DerhH1g+HT?c^XrDaN@*Ts)Im`bU; znu^KuwX;M1J$1;C{SieHV|PrVYkA-1Mbu1_TmIw1(mZ^z4NnxuoU#vHB8aR$0sA>OapOGBG|3enu(@%XY?|O|5^!sza-;vM1El*QjE+~HdlpmYrwqL_ ztx}`47o}{^n;aE@THAm<*4nv??Yy~G<6zuko-gyMRN(Z8RQ^%n#>1`+F)*F{PRm7j zYeyr> zf#cFcy9fm_lhZfVA5DawziB@jK*CXj&FeP|ohudSWnb3KyK0EM88%}bNj>fl?d(iU zM4?b$*d!e1rAqsW{>=h`bg(SyH~3oPH`YYRC-^t~!PJ#qcv$FD`yU8AMWXaOL63Qc zd&UlJ`cnkUFthefPc3`jcXU6)xCkkT9!uRXQe)l`v$iFz-G=Lz7x%dPhBTh*q1y~4 zR%LdS25P7w+l0kKLgR&TlHgT$zKfmPtu`lbQHBpsary}Q*5YJq?QE!dQ6j=m!;8)6 zmDU2MJl>^vZ=x~m9}a^2-dhTl4vV8Nw{#qRPwvCw6po5)=O_djTFsXdV17Gw<+ct0 zK-#tm*XAb**R~ry_CwS!oW4&~1DB?b-{GIQurU=(#G=kNWn|sQkEog+igjtg$5Ea= zIa^IqT$vCSnDr z=6SG-XEG*>Lx_hySqrH1>3911^9=`susWd|Px%%=j>d?r7(CCK4JW6dZ?-(~@|jiB>T*l0jRuY+bFMg3J~+pickOBMhrvA8 zmzCKqEw!)o3)`}iBZS%n*H+_(Gq1+UNon`Ruxv4(`Gi)JOxrco%*-L(O8mKfx zvjbU(ff);KUdQHU#wC@{Y4|K<;*{wkgp_TD#$B|woSPGSw7l=vv{|7K6uQsE9N_%f z*cX-c+|*O-ERqW1I@q1Y`dv9iRrvLC<^z~SANWNM^I3wHu7cm*8R(bS^K5>Vo#o>z zt>jTo-^s4O#(j%|h%nh@EC5x9(Bt0eE^26@c3k1U6QC#X>a8CtS#V;D;5sI9vH}B?})>y|=x}ZO$x%jUs4Gi<3(2J#%8{+%99fKH|{Chj)7IgiSlU)C9@xhKC(z_)dmF2ZTu9zI~F?y+(@ zVzIW==<-Y@jB>I+;3!RI@YaU$-J?1Dj`s)vAExu)dz|KDB8O2b-h(0>UoG+Nuy@k@+BzmCXM zDiDXM`>m=%uhy>JI*bx0ZgD==EOv79lpW#aX}S5b*_wT%#_9`5{^xZ*FuGrC))DU1 z=3_O$re9vbjCE=QFi?C0Pp&o_I_?NG`Ig6eZLxRdou4BMRrqhb7Z*sJRqhP2<3OU+I3Y$7^)T}AMDd1XWupzw%g<1SZLO_qE>D0O@VE$!n=B-r9Q^r@4 zbGswp1ySrO5Aqa1Av61l2ra&X6{E(2$V-_h(u?`TQ)W41=`DSji~$&N@A&A90g$ zr)R%7yoi{b@pl`M`*ALWQx(HqD17-i4&ub;Axri)qK<3*&^?;onte{Ku+$k2$(Z=V zlL#dh8#pEw*=VyRe-I}pqSGh4H)(*51k3@TFWYp{ZGPgxVA~}{J}ronSd$XRGbmVa ziqZn$5hWkv>x~3td=7O-rWPmsO34tIz(jajKFSX9>nb0o*YO%GQ^@Xy%$ubJnA3%R zqnwnHSe5oow@{eGEGSk^PizC?$a(Xq zCo@Nu9*ZcM?~e0%ve^>{eSrQ%wo8{ObNcvc#!%u8y%gSZ&Bt7S;P%C8An~_~8JDz& z@Sj`@9V0DAsXAatcMoiAaeS+AwXv^y;g@gL#Sy4gzd6Ch=jYS4SzJkT9>TB^F4$zX zmJb7t?mJtsUw0=SI(49?P=LA^8kl-`21TUa#2(mGs|@`bR&wuMQUmHl5491FI*XOF zP9i>p^ghqifel7fqR#rQ*IENwh47?4X)!RP!{V=dfNDTb_+&ZHhui9^Dd>kzC<_>JSQn@oEZ5oUDTnH2e05MYJ$%!AWZ{m*G_p592 z(6+X?RbTFBah{g@2VO!5Poo^j*$!);d8_Y{w#wgQP6HtSWJdn@)&bN?0pCLa$Eaf` zt6~Dm-FB}Zjj`(%I**{D)o|OTsoNT&<349 zA)LynPN^&O$81+H&*d%T`Qskr@|{ew)}<06CN}xY*C+%hqkVwl_YqD+jqRo{PQqBF z2VkSDr2|9LwcRQCJJ=PCCgL2|N4OYHT*J8Oh?>}OR8qzJLzf==(34|B$Hc?++K)<~ za2)3!TRj50vd1YsKH5G1x;wk5A71y`=K~UifC%{>`z$D%RwHg*Y_v5$YQ)#cMoyk3 zSf}|Nw`lj7La`CZhrDe3Ok!%OjlEAl$QS9{^?yyoh;CVADxx?MEe2V7BR_ zbzAA}I%SypRJ{6Di^3@!^tHl*aX@n@OycW&$ee(}_&=1V47nB-}5jUiE^cUT}P zR%2uEJ=P))Y~p^VdKQJpJZ{}(8Am+r10gC?@!c^jkEAnS^3U$*$Y*QC_?v9)WF716 z%&IqHsC)zN%#dJzk;&|72#y{NrJTl3?=)>Fz#vW&l(-Kpi5T5mZ%$M?V$spm)p__cb#|`qgzwllvtY*`a&J}av#sA} zljPj)*pIFYX|uGaB51+U~Z-A9$H(r)YOja9-QpPM#&I`Hk)j6H#o zkOW01o<8^sJ@mVlM-=@}0?POM1zrJ8v?{mAG1YtZ7gp!C77+t5|GztGg=Up-yvd_- ze6|Z1A=7iQH6)qmke#7u7!yrHF!5uxXo9*(Vowp0ibB*ss`1A8RsCBOlff>fGmCuDMNW6};ok&ZM z(Cd6hew3qgYb~{ooiO6vdSaLSRBFT{sJog(cZ`Y9Bf*f@Na=1~215p5838bit=@4pE4=^QBgQ9uK4mKOL8+`1 znCIv$S|w+ypioB;s$Hbo-$BFU_r<0QOo_J+Zx<_F4h_FT?x+0ZW!zOF4o*OOOy4o5 z_n&aW0I2#vwf>qwt=%^pyMlJ8bzAorR4dhc&BMrCRcn7#j0x1yuG-a<*ruR}=bPeq z_=9rgHGZG2G<=+k^s+}eA+69h7oW6ETy$$?tPA7TH7aD7Ylv1eDi8$mpaNQ0th1R^HK{ zgONO!yjDry!%k#Z=;c=DPR^VsQ?IZDThCMaJ}Rxe8@kC(aWa;zyWRU z5x1&CB`a4hFKFh3`#*OZAYK(2#^lvfwk#osI8Vx(y$bHR88@O5MdG>lgwSHLXwQWQ zv?wYD*0c7!Q5(0jDZOT{D)SmrJyCNpR|( z6RF6=R!WxdBNhT9UM61WLuv;i)Be%IAup*t|6hmbhAXg%k}dhMX{3g#O*GU?mW8#Lc`vgqIia(kp2Ir>?o#6PCHLLE(g9MSHB{eCL z#)8J;O0g;I^q=mz+7$q5q0oIxoEXwtLF1)J3rfP4P_UP)@ewDQt)Hc*r)+v?YRLne z(|L(J0H_Bbx189NGE$6kmzCGQSjhg7j1gR+r~Az*Q}&McGq6yQ;;v>*ANjVXJqa3* zPt27*EPi<6l{Q9M?|kOd=n?ACxSaWdiG4T6Hc&n(_4sgPKr4NU(E_*e(^-tH?RU^6^q%Cc&b!GaF7R6zq~Nk_hjiH#dUFmD`QGKRca~i(5VUkC)g|9Dt{717}-N-Q|k^ENz)1biH%K zcr~(}ewrCccGXwAdkZATec(+1x+oX~6GM8!RIc6z5#ezgi(YFYmmvR{puKOe1VZHA zuSDP>oG5O{V8?HDUpX$S3Q&0Eh8u}C86G9|{`Fp`FN-&;F7~xbTm5B*s`lRe=Ve+u zKYWLLEfagitEAai+Z83)3F!|~j0wH8HJr&L*A=q2$<;*hOj3}3tWBdV8uxxSpnbE zehF>+Ti+Le83X~in7~fzKK|j$K{0Mfq7z~N-i)qAE=_)};kY**VZ`oAGo=G$2mEOw zk8QnUA^XX%?`PjtajP1#6L{j%OuNIeU&Vp;TIiJH+G!)g6ZWyqNM4`*On@PDD{3di zmO}eMQ6V&6Txhr70f?Ci)HenAW8DSQFj1RGYmNG z85E5BIZfj%T-=B*Y1Nx+0}n1tK{XIBQtT21i2+HUB6Pt>@e0BzCwhzDD0VV4Na6C+ zzjjB?3SE(RP(ZjaWmeoXR}qy9Bb?a=?lfRgP`??Yzy5`aru%?SIIKwZ(lur>(iy7D zKbJW4hTmb5?MuDt&NA}D&*b`*o4JICB(k?n@J~+q#a(B<)fp16Q&28#s9i>>5VPfS zZ*r3>*@siVpXic2qoV7O68!{>ejKNVH_yJ!gi!xCgP_P32<~TME5zwWL2*tPe4kO> zm3Ka0DFf}4Veu|z?-v3UX?M*@ta6-MoLMw!D}zl#OIR)i*j^6g7O3jwxLi9zILx(~ z4w!T-n+N@I4+dUR&q-SjMqLT8=;@N`5It)LI0S=r zf)_d|_2upsai(~FnA#r$Dga@P1fGKIPldrN1D6$PUVqwlbhQRxnB^!SX7zL`f}_Hr zrXs^bon3!7aT7xozX3Z`Y|C`^T|2?Y-b;4UJ47KjxqLK6xE$4-#BF#opah2j~bfFz@)&2Ks-Yd!hu_yNN%_{euPfRNy zDs=#G6d!3%y+G4zxn28xjyoXknSv<+!6o*G%J+4@CLKfvBRqAF+h+&HWiQj!`$(gV zs*iSCAV2-5z4O@%%Eo?7&s{dZZvUMLsp|sk{Ms5M@m1F*U~qW5!GrU&Jg0 zmw=&4FU_<&5pG(MuR5hHmnO$*!kT;?WZfaZB0Hv@vxBu_t2n!TV(jIXcW>uvOZ)-1 z{G%7kJa0YD7USDzSrhde6WO>?E?c)Ne<|aZ9wv{5pk7;QT!K|GXt!9L zT%R4tt1TNt(M**O$DFX*8KeI+;ZP)EjRy{EF#8I{SFeJ2#W_k1){9HkZ$h!Mqe3dt@z?NK1Y*gf@ zbG9cYas+h7^`2}JJ@sONfmXY!DmY8HS^nP&B{p8*$`?;Q=3vjq^BSe7X<*UfYV-xp z-0og3+2ZUKp?p~2{7putA1)nl&gx05QofOBVKUw99?#{2j4}&i?qvygOCLLb`*v$t zHiKM&#`~9UOd0kK^B3f&&qU4Cz0H1TZdOJ;;2QQX=MGkO6ay#N z`Y_z#q0sHn%Khar(;JUt=HU;-ncO9!Si67=pVu7Pnrvrx!To#7{+;*Lr0!VN&#WsG z;kj;GmvUE-eI3)|qdM0k-S6yvmst5g;N|=0?|HqV&pl-q)?RCW3niX_>uVruDeMlE zFsd@eu>E>n*WPe?SM=$C*XNtxzdF~QpN7R7oHPMz65k*1G(I9Ozc7A@a@466 zJOBJ>oqo~wELsG3Jg9F4CPZ!nL3g7I+r8x-?pQZA?qfcrgwh}eHf%x+m_+q1u*zLpZJ1&kQ?|=;d%~WrJT%ke-2~*i*}DGz&#Orl!3!g j+-_EA+4skN)erxVR6T#OKhe2^0SG)@{an^LB{Ts5sdizZ literal 0 HcmV?d00001 diff --git a/docs/tutorials/qsimcirq_gcp.md b/docs/tutorials/qsimcirq_gcp.md index f204785c..4e292b3f 100644 --- a/docs/tutorials/qsimcirq_gcp.md +++ b/docs/tutorials/qsimcirq_gcp.md @@ -53,7 +53,7 @@ Then click on *Create* for a new VM instance: ![alt_text](../images/qsimcirq_gcp/image7.png ) -### Build a Container Optimized VM +### Build a Container Optimized VM with container deployed To create the VM use the steps in sequence below: @@ -72,14 +72,17 @@ To create the VM use the steps in sequence below: * Choose the [Machine Type](https://cloud.google.com/blog/products/compute/choose-the-right-google-compute-engine-machine-type-for-you): n2-standard-16 * 16 CPUs * 64GB memory -* Choose the Boot Disk image:[ Container-Optimized OS](https://cloud.google.com/container-optimized-os/docs/concepts/features-and-benefits) * Leave the remaining as defaults. ![alt_text](../images/qsimcirq_gcp/image10.png ) +> Select `Deploy a container image to this VM instance`. -Finally, enable HTTP access and click *Create*. - -![alt_text](../images/qsimcirq_gcp/image8.png ) +For the container image enter: +``` +gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest +``` +![alt_text](../images/qsimcirq_gcp/container.png ) +> This may take a few minutes to complete, even after the VM is created, the container will take some time to complete. ## Preparing your local system to connect to Colab @@ -145,32 +148,7 @@ You should now see the command line prompt from your VM: wellhello@qsim-1 ~ $ ``` -### Run the Jupyter / qsim container on your VM - -At the command prompt you can now start a Docker container with all the required -code to run simulations. Start the container: - -``` -$ docker run -v `pwd`:/homedir -p 8888:8888 gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest -``` - -You should see several lines of output ending with lines like below. (If you get -an error about `permission denied` you may need to run docker with `sudo` as -[described here](https://docs.docker.com/engine/reference/run/#general-form)). - - -``` -To access the notebook, open this file in a browser: - file:///root/.local/share/jupyter/runtime/nbserver-1-open.html -Or copy and paste one of these URLs: - http://e1f7a7cca9fa:8888/?token=aa16e1b6d3f51c58928037d34cc6854dac47347dd4c0eae5 - or http://127.0.0.1:8888/?token=aa16e1b6d3f51c58928037d34cc6854dac47347dd4c0eae5 -``` - -Copy the last URL in the output. Edit the URL to replace `127.0.0.1` with -`localhost`. Save this URL for the next step. This URL points to your local -runtime, running as a Docker container on your VM. - +The container port is now forwarded to your local machine. ## Connect Colab to your local runtime @@ -185,9 +163,9 @@ get the UI: Select *Connect to local runtime*. You will see the UI: - + -Pass the edited URL from the previous section, then click *Connect*: +Type in the URL `http://localhost:8888/` , then click *Connect*: @@ -213,7 +191,7 @@ In the previous step, you copied a URL like below. It is easy to just copy that URL and paste it directly into a browser running on your local machine. ``` -http://127.0.0.1:8888/?token=7191178ae9aa4ebe1698b07bb67dea1d289cfd0e0b960373 +http://127.0.0.1:8888/ ``` In the browser you should now see the Jupyter UI: @@ -230,11 +208,6 @@ You can now run these cells as you would in any notebook. ![alt_text](../images/qsimcirq_gcp/image1.png) -If you choose to modify the notebook, you can save it on the *qsim-1* VM from *File* -> *Save As*, and saving to `/homedir/mynotebook.ipynb`. This will save in your home directory on your VM. If you intend to destroy the VM after this tutorial, either download the notebooks from the VM or save directly from your browser. - -![alt_text](../images/qsimcirq_gcp/image4.png) - - ## Run interactively To run interactively within the container, you can open a second shell window to @@ -312,24 +285,6 @@ output vector: (0.5+0.5j)|0⟩ + (0.5-0.5j)|1⟩ You have successfully simulated a quantum circuit on Google Cloud Platform using a Singularity container. -### Running your own script - -If you want to run a Python script, you can locate a file in the home directory -on your VM, then run something like the following in the container shell: - -``` -$ python3 /homedir/myscript.py -``` - -### Exit the container - -Exit the container by typing ctrl-d twice. You will see the output like: - -``` -[root@79804d33f250 /]# exit -``` - - ## Clean up To avoid incurring charges to your Google Cloud Platform account for the diff --git a/jupyter/Dockerfile b/jupyter/Dockerfile index 29043611..2019390c 100644 --- a/jupyter/Dockerfile +++ b/jupyter/Dockerfile @@ -22,6 +22,5 @@ RUN jupyter serverextension enable --py jupyter_http_over_ws CMD ["jupyter-notebook", "--port=8888", "--no-browser",\ "--ip=0.0.0.0", "--allow-root", \ "--NotebookApp.allow_origin='*'", \ - "--NotebookApp.port_retries=0"] - - #"--NotebookApp.allow_origin='https://colab.research.google.com'", \ \ No newline at end of file + "--NotebookApp.port_retries=0", \ + "--NotebookApp.token=''"] From a12577c73207688b3673515393fad0d7c443f958 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Wed, 12 May 2021 19:14:09 +0000 Subject: [PATCH 002/246] Added multinode directory --- docs/tutorials/multinode/terraform/Makefile | 7 + docs/tutorials/multinode/terraform/README.md | 50 + .../tutorials/multinode/terraform/circuit_q24 | 6325 +++++++++++++++++ .../multinode/terraform/htcondor/resources.tf | 325 + .../htcondor/startup-centos-noinstall.sh | 72 + .../terraform/htcondor/startup-centos.sh | 169 + docs/tutorials/multinode/terraform/init.tf | 3 + docs/tutorials/multinode/terraform/main.tf | 19 + docs/tutorials/multinode/test.txt | 0 9 files changed, 6970 insertions(+) create mode 100644 docs/tutorials/multinode/terraform/Makefile create mode 100644 docs/tutorials/multinode/terraform/README.md create mode 100644 docs/tutorials/multinode/terraform/circuit_q24 create mode 100644 docs/tutorials/multinode/terraform/htcondor/resources.tf create mode 100644 docs/tutorials/multinode/terraform/htcondor/startup-centos-noinstall.sh create mode 100644 docs/tutorials/multinode/terraform/htcondor/startup-centos.sh create mode 100644 docs/tutorials/multinode/terraform/init.tf create mode 100644 docs/tutorials/multinode/terraform/main.tf create mode 100644 docs/tutorials/multinode/test.txt diff --git a/docs/tutorials/multinode/terraform/Makefile b/docs/tutorials/multinode/terraform/Makefile new file mode 100644 index 00000000..ebd5018d --- /dev/null +++ b/docs/tutorials/multinode/terraform/Makefile @@ -0,0 +1,7 @@ +.PHONY: apply destroy + +apply: + terraform apply -auto-approve + +destroy: + terraform destroy -auto-approve diff --git a/docs/tutorials/multinode/terraform/README.md b/docs/tutorials/multinode/terraform/README.md new file mode 100644 index 00000000..bbcec480 --- /dev/null +++ b/docs/tutorials/multinode/terraform/README.md @@ -0,0 +1,50 @@ +# HTCondor on GCP + +``` +gcloud config set project [[PROJECT_ID]] +gcloud config set compute/region [[YOUR_REGION]] +gcloud config set compute/zone [[YOUR_ZONE]] +``` + +``` +git clone https://github.com/lsst-dm/google-poc-2020.git +cd google-poc-2020 +``` + +``` +gcloud iam service-accounts create htcondor +``` + +``` +gcloud projects add-iam-policy-binding \ + --member serviceAccount:htcondor@[[PROJECT]] \ + --role roles/iam.serviceAccountUser +``` + + +## Updating the main.tf Terraform file + +The as-is file from the git repository will not work without modifying the _main.tf_ to the values relevant to your project. + +``` +variable "cluster_name" { + type = string + default = "condor" + description = "Name used to prefix resources in cluster." + +} + +module "htcondor" { + source = "./htcondor/" + cluster_name = var.cluster_name + osimage = "lsst" + osversion = "7" + bucket_name = "[[GCS_FUSE_BUCKET]]" + zone="[[YOUR_ZONE]]" + project="[[PROJECT_ID]]" + max_replicas=20 + min_replicas=1 + service_account="htcondor@[[PROJECT_ID]].iam.gserviceaccount.com" + use_preemptibles=true +} +``` diff --git a/docs/tutorials/multinode/terraform/circuit_q24 b/docs/tutorials/multinode/terraform/circuit_q24 new file mode 100644 index 00000000..757895d7 --- /dev/null +++ b/docs/tutorials/multinode/terraform/circuit_q24 @@ -0,0 +1,6325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + qsim/circuit_q24 at master · quantumlib/qsim · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ +
+ + + + + +
+ + + +
+ + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + + Permalink + + + +
+ +
+
+ + + master + + + + +
+
+
+ Switch branches/tags + +
+ + + +
+ +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + +
+ + +
+
+
+
+ +
+ +
+ + + + Go to file + + +
+ + + + + + + + + +
+
+
+ + + +
+ +
+
+ + @sergeisakov + +
+ + + + + + +
+
+ + Latest commit + 598c0b0 + Feb 7, 2020 + + + + + + History + + +
+
+ +
+ +
+
+ + + 1 + + contributor + + +
+ +

+ Users who have contributed to this file +

+
+ + + + + + +
+
+
+
+ + + + + + + + + +
+ +
+ + +
+ + 1258 lines (1258 sloc) + + 12.8 KB +
+ + + +
+ +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
24
0 h 0
0 h 1
0 h 2
0 h 3
0 h 4
0 h 5
0 h 6
0 h 7
0 h 8
0 h 9
0 h 10
0 h 11
0 h 12
0 h 13
0 h 14
0 h 15
0 h 16
0 h 17
0 h 18
0 h 19
0 h 20
0 h 21
0 h 22
0 h 23
1 cz 0 1
1 cz 4 5
1 cz 8 9
1 cz 12 13
1 cz 16 17
1 cz 20 21
2 cz 2 3
2 cz 6 7
2 cz 10 11
2 cz 14 15
2 cz 18 19
2 cz 22 23
2 t 0
2 t 1
2 t 4
2 t 5
2 t 8
2 t 9
2 t 12
2 t 13
2 t 16
2 t 17
2 t 20
2 t 21
3 cz 6 12
3 cz 8 14
3 cz 10 16
3 t 2
3 t 3
3 t 7
3 t 11
3 t 15
3 t 18
3 t 19
3 t 22
3 t 23
4 cz 7 13
4 cz 9 15
4 cz 11 17
4 t 6
4 x_1_2 8
4 t 10
4 y_1_2 12
4 t 14
4 y_1_2 16
5 cz 1 2
5 cz 9 10
5 cz 13 14
5 cz 21 22
5 x_1_2 7
5 y_1_2 11
5 y_1_2 15
5 y_1_2 17
6 cz 3 4
6 cz 7 8
6 cz 15 16
6 cz 19 20
6 y_1_2 1
6 y_1_2 2
6 y_1_2 9
6 y_1_2 10
6 x_1_2 13
6 x_1_2 14
6 y_1_2 21
6 x_1_2 22
7 cz 0 6
7 cz 2 8
7 cz 4 10
7 cz 13 19
7 cz 15 21
7 cz 17 23
7 x_1_2 3
7 y_1_2 7
7 x_1_2 16
7 x_1_2 20
8 cz 1 7
8 cz 3 9
8 cz 5 11
8 cz 12 18
8 cz 14 20
8 cz 16 22
8 y_1_2 0
8 x_1_2 2
8 y_1_2 4
8 y_1_2 6
8 y_1_2 8
8 x_1_2 10
8 t 13
8 t 15
8 t 17
8 y_1_2 19
8 x_1_2 21
8 y_1_2 23
9 cz 0 1
9 cz 4 5
9 cz 8 9
9 cz 12 13
9 cz 16 17
9 cz 20 21
9 y_1_2 3
9 t 7
9 x_1_2 11
9 t 14
9 y_1_2 18
9 y_1_2 22
10 cz 2 3
10 cz 6 7
10 cz 10 11
10 cz 14 15
10 cz 18 19
10 cz 22 23
10 t 0
10 t 1
10 x_1_2 4
10 x_1_2 5
10 t 8
10 x_1_2 9
10 t 12
10 y_1_2 13
10 t 16
10 y_1_2 17
10 t 20
10 y_1_2 21
11 cz 6 12
11 cz 8 14
11 cz 10 16
11 t 2
11 x_1_2 3
11 y_1_2 7
11 t 11
11 y_1_2 15
11 t 18
11 x_1_2 19
11 t 22
11 x_1_2 23
12 cz 7 13
12 cz 9 15
12 cz 11 17
12 x_1_2 6
12 y_1_2 8
12 t 10
12 x_1_2 12
12 y_1_2 14
12 x_1_2 16
13 cz 1 2
13 cz 9 10
13 cz 13 14
13 cz 21 22
13 t 7
13 x_1_2 11
13 x_1_2 15
13 x_1_2 17
14 cz 3 4
14 cz 7 8
14 cz 15 16
14 cz 19 20
14 x_1_2 1
14 x_1_2 2
14 t 9
14 y_1_2 10
14 x_1_2 13
14 x_1_2 14
14 x_1_2 21
14 y_1_2 22
15 cz 0 6
15 cz 2 8
15 cz 4 10
15 cz 13 19
15 cz 15 21
15 cz 17 23
15 t 3
15 x_1_2 7
15 t 16
15 x_1_2 20
16 cz 1 7
16 cz 3 9
16 cz 5 11
16 cz 12 18
16 cz 14 20
16 cz 16 22
16 x_1_2 0
16 t 2
16 y_1_2 4
16 t 6
16 t 8
16 t 10
16 t 13
16 t 15
16 t 17
16 y_1_2 19
16 t 21
16 t 23
17 cz 0 1
17 cz 4 5
17 cz 8 9
17 cz 12 13
17 cz 16 17
17 cz 20 21
17 x_1_2 3
17 y_1_2 7
17 t 11
17 y_1_2 14
17 x_1_2 18
17 t 22
18 cz 2 3
18 cz 6 7
18 cz 10 11
18 cz 14 15
18 cz 18 19
18 cz 22 23
18 t 0
18 y_1_2 1
18 t 4
18 y_1_2 5
18 x_1_2 8
18 y_1_2 9
18 y_1_2 12
18 x_1_2 13
18 x_1_2 16
18 y_1_2 17
18 t 20
18 x_1_2 21
19 cz 6 12
19 cz 8 14
19 cz 10 16
19 y_1_2 2
19 y_1_2 3
19 x_1_2 7
19 x_1_2 11
19 x_1_2 15
19 y_1_2 18
19 t 19
19 x_1_2 22
19 y_1_2 23
20 cz 7 13
20 cz 9 15
20 cz 11 17
20 x_1_2 6
20 t 8
20 y_1_2 10
20 t 12
20 t 14
20 t 16
21 cz 1 2
21 cz 9 10
21 cz 13 14
21 cz 21 22
21 y_1_2 7
21 t 11
21 t 15
21 t 17
22 cz 3 4
22 cz 7 8
22 cz 15 16
22 cz 19 20
22 t 1
22 x_1_2 2
22 t 9
22 t 10
22 y_1_2 13
22 x_1_2 14
22 t 21
22 y_1_2 22
23 cz 0 6
23 cz 2 8
23 cz 4 10
23 cz 13 19
23 cz 15 21
23 cz 17 23
23 x_1_2 3
23 x_1_2 7
23 x_1_2 16
23 y_1_2 20
24 cz 1 7
24 cz 3 9
24 cz 5 11
24 cz 12 18
24 cz 14 20
24 cz 16 22
24 y_1_2 0
24 y_1_2 2
24 x_1_2 4
24 t 6
24 x_1_2 8
24 y_1_2 10
24 t 13
24 y_1_2 15
24 y_1_2 17
24 x_1_2 19
24 x_1_2 21
24 x_1_2 23
25 cz 0 1
25 cz 4 5
25 cz 8 9
25 cz 12 13
25 cz 16 17
25 cz 20 21
25 t 3
25 y_1_2 7
25 y_1_2 11
25 t 14
25 t 18
25 x_1_2 22
26 cz 2 3
26 cz 6 7
26 cz 10 11
26 cz 14 15
26 cz 18 19
26 cz 22 23
26 t 0
26 x_1_2 1
26 y_1_2 4
26 t 5
26 y_1_2 8
26 y_1_2 9
26 y_1_2 12
26 x_1_2 13
26 y_1_2 16
26 t 17
26 x_1_2 20
26 t 21
27 cz 6 12
27 cz 8 14
27 cz 10 16
27 x_1_2 2
27 y_1_2 3
27 x_1_2 7
27 t 11
27 x_1_2 15
27 y_1_2 18
27 y_1_2 19
27 y_1_2 22
27 y_1_2 23
28 cz 7 13
28 cz 9 15
28 cz 11 17
28 y_1_2 6
28 x_1_2 8
28 t 10
28 x_1_2 12
28 y_1_2 14
28 x_1_2 16
29 cz 1 2
29 cz 9 10
29 cz 13 14
29 cz 21 22
29 y_1_2 7
29 x_1_2 11
29 y_1_2 15
29 x_1_2 17
30 cz 3 4
30 cz 7 8
30 cz 15 16
30 cz 19 20
30 t 1
30 y_1_2 2
30 x_1_2 9
30 y_1_2 10
30 y_1_2 13
30 x_1_2 14
30 x_1_2 21
30 t 22
31 cz 0 6
31 cz 2 8
31 cz 4 10
31 cz 13 19
31 cz 15 21
31 cz 17 23
31 x_1_2 3
31 x_1_2 7
31 t 16
31 y_1_2 20
32 cz 1 7
32 cz 3 9
32 cz 5 11
32 cz 12 18
32 cz 14 20
32 cz 16 22
32 y_1_2 0
32 x_1_2 2
32 x_1_2 4
32 t 6
32 t 8
32 x_1_2 10
32 x_1_2 13
32 x_1_2 15
32 t 17
32 t 19
32 y_1_2 21
32 x_1_2 23
33 cz 0 1
33 cz 4 5
33 cz 8 9
33 cz 12 13
33 cz 16 17
33 cz 20 21
33 y_1_2 3
33 y_1_2 7
33 y_1_2 11
33 t 14
33 x_1_2 18
33 y_1_2 22
34 cz 2 3
34 cz 6 7
34 cz 10 11
34 cz 14 15
34 cz 18 19
34 cz 22 23
34 x_1_2 0
34 x_1_2 1
34 y_1_2 4
34 y_1_2 5
34 y_1_2 8
34 t 9
34 y_1_2 12
34 y_1_2 13
34 y_1_2 16
34 y_1_2 17
34 t 20
34 t 21
35 cz 6 12
35 cz 8 14
35 cz 10 16
35 y_1_2 2
35 x_1_2 3
35 x_1_2 7
35 t 11
35 t 15
35 y_1_2 18
35 y_1_2 19
35 x_1_2 22
35 y_1_2 23
36 cz 7 13
36 cz 9 15
36 cz 11 17
36 y_1_2 6
36 x_1_2 8
36 t 10
36 t 12
36 y_1_2 14
36 t 16
37 cz 1 2
37 cz 9 10
37 cz 13 14
37 cz 21 22
37 y_1_2 7
37 x_1_2 11
37 x_1_2 15
37 t 17
38 cz 3 4
38 cz 7 8
38 cz 15 16
38 cz 19 20
38 t 1
38 t 2
38 x_1_2 9
38 y_1_2 10
38 t 13
38 t 14
38 y_1_2 21
38 y_1_2 22
39 cz 0 6
39 cz 2 8
39 cz 4 10
39 cz 13 19
39 cz 15 21
39 cz 17 23
39 y_1_2 3
39 t 7
39 y_1_2 16
39 x_1_2 20
40 cz 1 7
40 cz 3 9
40 cz 5 11
40 cz 12 18
40 cz 14 20
40 cz 16 22
40 y_1_2 0
40 x_1_2 2
40 t 4
40 t 6
40 y_1_2 8
40 t 10
40 y_1_2 13
40 t 15
40 y_1_2 17
40 t 19
40 x_1_2 21
40 x_1_2 23
41 cz 0 1
41 cz 4 5
41 cz 8 9
41 cz 12 13
41 cz 16 17
41 cz 20 21
41 x_1_2 3
41 y_1_2 7
41 y_1_2 11
41 y_1_2 14
41 x_1_2 18
41 t 22
42 cz 2 3
42 cz 6 7
42 cz 10 11
42 cz 14 15
42 cz 18 19
42 cz 22 23
42 t 0
42 x_1_2 1
42 x_1_2 4
42 x_1_2 5
42 t 8
42 y_1_2 9
42 x_1_2 12
42 t 13
42 t 16
42 t 17
42 t 20
42 y_1_2 21
43 cz 6 12
43 cz 8 14
43 cz 10 16
43 t 2
43 y_1_2 3
43 x_1_2 7
43 x_1_2 11
43 x_1_2 15
43 t 18
43 y_1_2 19
43 y_1_2 22
43 y_1_2 23
44 cz 7 13
44 cz 9 15
44 cz 11 17
44 y_1_2 6
44 x_1_2 8
44 x_1_2 10
44 t 12
44 t 14
44 y_1_2 16
45 cz 1 2
45 cz 9 10
45 cz 13 14
45 cz 21 22
45 t 7
45 t 11
45 t 15
45 x_1_2 17
46 cz 3 4
46 cz 7 8
46 cz 15 16
46 cz 19 20
46 y_1_2 1
46 y_1_2 2
46 t 9
46 t 10
46 y_1_2 13
46 y_1_2 14
46 t 21
46 t 22
47 cz 0 6
47 cz 2 8
47 cz 4 10
47 cz 13 19
47 cz 15 21
47 cz 17 23
47 t 3
47 y_1_2 7
47 t 16
47 x_1_2 20
48 cz 1 7
48 cz 3 9
48 cz 5 11
48 cz 12 18
48 cz 14 20
48 cz 16 22
48 x_1_2 0
48 x_1_2 2
48 y_1_2 4
48 t 6
48 t 8
48 y_1_2 10
48 x_1_2 13
48 y_1_2 15
48 t 17
48 t 19
48 y_1_2 21
48 t 23
49 cz 0 1
49 cz 4 5
49 cz 8 9
49 cz 12 13
49 cz 16 17
49 cz 20 21
49 y_1_2 3
49 t 7
49 x_1_2 11
49 x_1_2 14
49 x_1_2 18
49 x_1_2 22
50 cz 2 3
50 cz 6 7
50 cz 10 11
50 cz 14 15
50 cz 18 19
50 cz 22 23
50 t 0
50 t 1
50 x_1_2 4
50 t 5
50 x_1_2 8
50 x_1_2 9
50 y_1_2 12
50 y_1_2 13
50 y_1_2 16
50 x_1_2 17
50 t 20
50 x_1_2 21
51 cz 6 12
51 cz 8 14
51 cz 10 16
51 y_1_2 2
51 x_1_2 3
51 x_1_2 7
51 t 11
51 t 15
51 t 18
51 x_1_2 19
51 t 22
51 x_1_2 23
52 cz 7 13
52 cz 9 15
52 cz 11 17
52 y_1_2 6
52 y_1_2 8
52 x_1_2 10
52 x_1_2 12
52 y_1_2 14
52 t 16
53 cz 1 2
53 cz 9 10
53 cz 13 14
53 cz 21 22
53 t 7
53 y_1_2 11
53 x_1_2 15
53 t 17
54 cz 3 4
54 cz 7 8
54 cz 15 16
54 cz 19 20
54 x_1_2 1
54 x_1_2 2
54 t 9
54 t 10
54 t 13
54 x_1_2 14
54 t 21
54 y_1_2 22
55 cz 0 6
55 cz 2 8
55 cz 4 10
55 cz 13 19
55 cz 15 21
55 cz 17 23
55 y_1_2 3
55 y_1_2 7
55 x_1_2 16
55 y_1_2 20
56 cz 1 7
56 cz 3 9
56 cz 5 11
56 cz 12 18
56 cz 14 20
56 cz 16 22
56 y_1_2 0
56 y_1_2 2
56 y_1_2 4
56 t 6
56 t 8
56 x_1_2 10
56 y_1_2 13
56 t 15
56 y_1_2 17
56 t 19
56 y_1_2 21
56 t 23
57 cz 0 1
57 cz 4 5
57 cz 8 9
57 cz 12 13
57 cz 16 17
57 cz 20 21
57 x_1_2 3
57 x_1_2 7
57 t 11
57 y_1_2 14
57 y_1_2 18
57 x_1_2 22
58 cz 2 3
58 cz 6 7
58 cz 10 11
58 cz 14 15
58 cz 18 19
58 cz 22 23
58 x_1_2 0
58 t 1
58 t 4
58 y_1_2 5
58 y_1_2 8
58 y_1_2 9
58 y_1_2 12
58 t 13
58 t 16
58 t 17
58 x_1_2 20
58 x_1_2 21
59 cz 6 12
59 cz 8 14
59 cz 10 16
59 t 2
59 t 3
59 y_1_2 7
59 y_1_2 11
59 y_1_2 15
59 t 18
59 x_1_2 19
59 y_1_2 22
59 x_1_2 23
60 cz 7 13
60 cz 9 15
60 cz 11 17
60 y_1_2 6
60 x_1_2 8
60 y_1_2 10
60 x_1_2 12
60 x_1_2 14
60 x_1_2 16
61 cz 1 2
61 cz 9 10
61 cz 13 14
61 cz 21 22
61 x_1_2 7
61 t 11
61 t 15
61 y_1_2 17
62 cz 3 4
62 cz 7 8
62 cz 15 16
62 cz 19 20
62 x_1_2 1
62 x_1_2 2
62 t 9
62 t 10
62 y_1_2 13
62 y_1_2 14
62 t 21
62 x_1_2 22
63 cz 0 6
63 cz 2 8
63 cz 4 10
63 cz 13 19
63 cz 15 21
63 cz 17 23
63 y_1_2 3
63 y_1_2 7
63 t 16
63 t 20
64 cz 1 7
64 cz 3 9
64 cz 5 11
64 cz 12 18
64 cz 14 20
64 cz 16 22
64 t 0
64 y_1_2 2
64 y_1_2 4
64 x_1_2 6
64 y_1_2 8
64 y_1_2 10
64 x_1_2 13
64 y_1_2 15
64 x_1_2 17
64 y_1_2 19
64 y_1_2 21
64 y_1_2 23
65 cz 0 1
65 cz 4 5
65 cz 8 9
65 cz 12 13
65 cz 16 17
65 cz 20 21
65 x_1_2 3
65 x_1_2 7
65 x_1_2 11
65 x_1_2 14
65 x_1_2 18
65 y_1_2 22
66 cz 2 3
66 cz 6 7
66 cz 10 11
66 cz 14 15
66 cz 18 19
66 cz 22 23
66 x_1_2 0
66 y_1_2 1
66 t 4
66 x_1_2 5
66 t 8
66 y_1_2 9
66 y_1_2 12
66 t 13
66 y_1_2 16
66 t 17
66 x_1_2 20
66 x_1_2 21
67 cz 6 12
67 cz 8 14
67 cz 10 16
67 t 2
67 t 3
67 t 7
67 t 11
67 x_1_2 15
67 t 18
67 x_1_2 19
67 t 22
67 x_1_2 23
68 cz 7 13
68 cz 9 15
68 cz 11 17
68 y_1_2 6
68 x_1_2 8
68 x_1_2 10
68 t 12
68 t 14
68 t 16
69 cz 1 2
69 cz 9 10
69 cz 13 14
69 cz 21 22
69 y_1_2 7
69 y_1_2 11
69 y_1_2 15
69 y_1_2 17
70 cz 3 4
70 cz 7 8
70 cz 15 16
70 cz 19 20
70 x_1_2 1
70 y_1_2 2
70 x_1_2 9
70 t 10
70 x_1_2 13
70 y_1_2 14
70 t 21
70 x_1_2 22
71 cz 0 6
71 cz 2 8
71 cz 4 10
71 cz 13 19
71 cz 15 21
71 cz 17 23
71 x_1_2 3
71 x_1_2 7
71 y_1_2 16
71 t 20
72 cz 1 7
72 cz 3 9
72 cz 5 11
72 cz 12 18
72 cz 14 20
72 cz 16 22
72 t 0
72 x_1_2 2
72 x_1_2 4
72 t 6
72 y_1_2 8
72 x_1_2 10
72 t 13
72 x_1_2 15
72 x_1_2 17
72 y_1_2 19
72 y_1_2 21
72 y_1_2 23
73 cz 0 1
73 cz 4 5
73 cz 8 9
73 cz 12 13
73 cz 16 17
73 cz 20 21
73 t 3
73 y_1_2 7
73 t 11
73 x_1_2 14
73 y_1_2 18
73 t 22
74 cz 2 3
74 cz 6 7
74 cz 10 11
74 cz 14 15
74 cz 18 19
74 cz 22 23
74 x_1_2 0
74 y_1_2 1
74 t 4
74 y_1_2 5
74 t 8
74 y_1_2 9
74 y_1_2 12
74 x_1_2 13
74 x_1_2 16
74 y_1_2 17
74 x_1_2 20
74 t 21
75 cz 6 12
75 cz 8 14
75 cz 10 16
75 t 2
75 x_1_2 3
75 t 7
75 x_1_2 11
75 t 15
75 t 18
75 x_1_2 19
75 y_1_2 22
75 x_1_2 23
76 cz 7 13
76 cz 9 15
76 cz 11 17
76 x_1_2 6
76 x_1_2 8
76 y_1_2 10
76 x_1_2 12
76 t 14
76 t 16
77 cz 1 2
77 cz 9 10
77 cz 13 14
77 cz 21 22
77 x_1_2 7
77 y_1_2 11
77 x_1_2 15
77 x_1_2 17
78 cz 3 4
78 cz 7 8
78 cz 15 16
78 cz 19 20
78 t 1
78 y_1_2 2
78 x_1_2 9
78 x_1_2 10
78 t 13
78 x_1_2 14
78 y_1_2 21
78 t 22
79 cz 0 6
79 cz 2 8
79 cz 4 10
79 cz 13 19
79 cz 15 21
79 cz 17 23
79 t 3
79 t 7
79 y_1_2 16
79 t 20
80 cz 1 7
80 cz 3 9
80 cz 5 11
80 cz 12 18
80 cz 14 20
80 cz 16 22
80 t 0
80 t 2
80 y_1_2 4
80 t 6
80 t 8
80 y_1_2 10
80 x_1_2 13
80 t 15
80 t 17
80 t 19
80 t 21
80 y_1_2 23
81 cz 0 1
81 cz 4 5
81 cz 8 9
81 cz 12 13
81 cz 16 17
81 cz 20 21
81 x_1_2 3
81 y_1_2 7
81 x_1_2 11
81 y_1_2 14
81 y_1_2 18
81 x_1_2 22
82 cz 2 3
82 cz 6 7
82 cz 10 11
82 cz 14 15
82 cz 18 19
82 cz 22 23
82 y_1_2 0
82 x_1_2 1
82 t 4
82 x_1_2 5
82 x_1_2 8
82 t 9
82 y_1_2 12
82 y_1_2 13
82 x_1_2 16
82 x_1_2 17
82 x_1_2 20
82 y_1_2 21
83 cz 6 12
83 cz 8 14
83 cz 10 16
83 x_1_2 2
83 t 3
83 x_1_2 7
83 y_1_2 11
83 y_1_2 15
83 t 18
83 y_1_2 19
83 t 22
83 x_1_2 23
84 cz 7 13
84 cz 9 15
84 cz 11 17
84 x_1_2 6
84 t 8
84 x_1_2 10
84 t 12
84 t 14
84 t 16
85 cz 1 2
85 cz 9 10
85 cz 13 14
85 cz 21 22
85 t 7
85 x_1_2 11
85 x_1_2 15
85 t 17
86 cz 3 4
86 cz 7 8
86 cz 15 16
86 cz 19 20
86 y_1_2 1
86 y_1_2 2
86 y_1_2 9
86 t 10
86 t 13
86 x_1_2 14
86 x_1_2 21
86 x_1_2 22
87 cz 0 6
87 cz 2 8
87 cz 4 10
87 cz 13 19
87 cz 15 21
87 cz 17 23
87 y_1_2 3
87 y_1_2 7
87 x_1_2 16
87 t 20
88 cz 1 7
88 cz 3 9
88 cz 5 11
88 cz 12 18
88 cz 14 20
88 cz 16 22
88 x_1_2 0
88 x_1_2 2
88 x_1_2 4
88 t 6
88 y_1_2 8
88 x_1_2 10
88 y_1_2 13
88 y_1_2 15
88 y_1_2 17
88 x_1_2 19
88 y_1_2 21
88 t 23
89 cz 0 1
89 cz 4 5
89 cz 8 9
89 cz 12 13
89 cz 16 17
89 cz 20 21
89 x_1_2 3
89 x_1_2 7
89 y_1_2 11
89 y_1_2 14
89 x_1_2 18
89 t 22
90 cz 2 3
90 cz 6 7
90 cz 10 11
90 cz 14 15
90 cz 18 19
90 cz 22 23
90 t 0
90 t 1
90 t 4
90 t 5
90 t 8
90 t 9
90 y_1_2 12
90 x_1_2 13
90 y_1_2 16
90 x_1_2 17
90 x_1_2 20
90 t 21
91 cz 6 12
91 cz 8 14
91 cz 10 16
91 t 2
91 t 3
91 t 7
91 x_1_2 11
91 t 15
91 y_1_2 18
91 y_1_2 19
91 y_1_2 22
91 y_1_2 23
92 cz 7 13
92 cz 9 15
92 cz 11 17
92 y_1_2 6
92 x_1_2 8
92 t 10
92 t 12
92 t 14
92 x_1_2 16
93 cz 1 2
93 cz 9 10
93 cz 13 14
93 cz 21 22
93 y_1_2 7
93 t 11
93 y_1_2 15
93 t 17
94 cz 3 4
94 cz 7 8
94 cz 15 16
94 cz 19 20
94 y_1_2 1
94 x_1_2 2
94 x_1_2 9
94 y_1_2 10
94 y_1_2 13
94 x_1_2 14
94 x_1_2 21
94 t 22
95 cz 0 6
95 cz 2 8
95 cz 4 10
95 cz 13 19
95 cz 15 21
95 cz 17 23
95 x_1_2 3
95 t 7
95 y_1_2 16
95 t 20
96 cz 1 7
96 cz 3 9
96 cz 5 11
96 cz 12 18
96 cz 14 20
96 cz 16 22
96 x_1_2 0
96 t 2
96 y_1_2 4
96 x_1_2 6
96 t 8
96 x_1_2 10
96 x_1_2 13
96 x_1_2 15
96 y_1_2 17
96 x_1_2 19
96 t 21
96 x_1_2 23
97 cz 0 1
97 cz 4 5
97 cz 8 9
97 cz 12 13
97 cz 16 17
97 cz 20 21
97 t 3
97 y_1_2 7
97 x_1_2 11
97 t 14
97 t 18
97 x_1_2 22
98 cz 2 3
98 cz 6 7
98 cz 10 11
98 cz 14 15
98 cz 18 19
98 cz 22 23
98 t 0
98 t 1
98 t 4
98 y_1_2 5
98 y_1_2 8
98 y_1_2 9
98 x_1_2 12
98 t 13
98 t 16
98 t 17
98 y_1_2 20
98 x_1_2 21
99 cz 6 12
99 cz 8 14
99 cz 10 16
99 y_1_2 2
99 x_1_2 3
99 x_1_2 7
99 t 11
99 y_1_2 15
99 y_1_2 18
99 y_1_2 19
99 t 22
99 t 23
100 cz 7 13
100 cz 9 15
100 cz 11 17
100 y_1_2 6
100 t 8
100 t 10
100 y_1_2 12
100 y_1_2 14
100 x_1_2 16
+ + + +
+ +
+ + + + +
+ + +
+ + +
+
+ + +
+ + + +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + diff --git a/docs/tutorials/multinode/terraform/htcondor/resources.tf b/docs/tutorials/multinode/terraform/htcondor/resources.tf new file mode 100644 index 00000000..c5bf4949 --- /dev/null +++ b/docs/tutorials/multinode/terraform/htcondor/resources.tf @@ -0,0 +1,325 @@ +variable "bucket_name" { + type = string + default = "" +} +variable "cluster_name" { + type = string + default = "condor" +} +variable "admin_email" { + type = string + default = "" +} +variable "osversion" { + type = string + default = "7" +} +variable "osimage" { + type = string + default = "hpc-centos-7" +} +variable "osproject" { + type = string + default = "click-to-deploy-images" +} +variable "condorversion" { + type = string + default = "" +} +variable "project" { + type = string +} +variable "zone" { + type = string +} +variable "min_replicas" { + type = number + default = 1 +} +variable "max_replicas" { + type = number + default = 20 +} +variable "use_preemptibles" { + type = bool + default = true +} +variable "metric_target_loadavg" { + type = number + default = "1.0" +} +variable "metric_target_queue" { + type = number + default = 10 +} +variable "compute_instance_type" { + type = string + default = "n2-standard-80" +} +variable "instance_type" { + type = string + default = "n2-standard-4" +} +variable "service_account" { + type = string + default = "default" +} +locals{ + compute_startup = templatefile( + "${path.module}/startup-centos.sh", + { + "bucket_name" = var.bucket_name, + "project" = var.project, + "cluster_name" = var.cluster_name, + "htserver_type" = "compute", + "osversion" = var.osversion, + "condorversion" = var.condorversion, + "admin_email" = var.admin_email + }) + submit_startup = templatefile( + "${path.module}/startup-centos.sh", + { + "bucket_name" = var.bucket_name, + "project" = var.project, + "cluster_name" = var.cluster_name, + "htserver_type" = "submit", + "osversion" = var.osversion, + "condorversion" = var.condorversion, + "admin_email" = var.admin_email + }) + master_startup = templatefile( + "${path.module}/startup-centos.sh", + { + "bucket_name" = var.bucket_name, + "project" = var.project, + "cluster_name" = var.cluster_name, + "htserver_type" = "master", + "osversion" = var.osversion, + "condorversion" = var.condorversion, + "admin_email" = var.admin_email + }) +} +data "google_compute_image" "startimage" { + family = var.osimage + project = var.osproject +} +resource "google_compute_instance" "condor-master" { + boot_disk { + auto_delete = "true" + device_name = "boot" + + initialize_params { + image = data.google_compute_image.startimage.self_link + size = "200" + type = "pd-standard" + } + + mode = "READ_WRITE" + } + + can_ip_forward = "false" + deletion_protection = "false" + enable_display = "false" + + machine_type = var.instance_type + metadata_startup_script = local.master_startup + name = "${var.cluster_name}-master" + network_interface { + access_config { + network_tier = "PREMIUM" + } + + network = "default" + #network_ip = "10.128.0.2" + subnetwork = "default" + subnetwork_project = var.project + } + + project = var.project + + scheduling { + automatic_restart = "true" + on_host_maintenance = "MIGRATE" + preemptible = "false" + } + + service_account { + email = var.service_account + scopes = ["https://www.googleapis.com/auth/cloud-platform"] + } + + shielded_instance_config { + enable_integrity_monitoring = "true" + enable_secure_boot = "false" + enable_vtpm = "true" + } + + tags = ["${var.cluster_name}-master"] + zone = var.zone +} + +resource "google_compute_instance" "condor-submit" { + boot_disk { + auto_delete = "true" + device_name = "boot" + + initialize_params { + image = data.google_compute_image.startimage.self_link + size = "200" + type = "pd-standard" + } + + mode = "READ_WRITE" + } + + can_ip_forward = "false" + deletion_protection = "false" + enable_display = "false" + + labels = { + goog-dm = "mycondorcluster" + } + + machine_type = var.instance_type + metadata_startup_script = local.submit_startup + name = "${var.cluster_name}-submit" + + network_interface { + access_config { + network_tier = "PREMIUM" + } + + network = "default" + #network_ip = "10.128.0.3" + subnetwork = "default" + subnetwork_project = var.project + } + + project = var.project + + scheduling { + automatic_restart = "true" + on_host_maintenance = "MIGRATE" + preemptible = "false" + } + + service_account { + email = var.service_account + # email = "487217491196-compute@developer.gserviceaccount.com" + scopes = ["https://www.googleapis.com/auth/monitoring.write", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/servicecontrol", "https://www.googleapis.com/auth/devstorage.read_only", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/service.management.readonly", "https://www.googleapis.com/auth/trace.append"] + } + + shielded_instance_config { + enable_integrity_monitoring = "true" + enable_secure_boot = "false" + enable_vtpm = "true" + } + + tags = ["${var.cluster_name}-submit"] + zone = var.zone +} +resource "google_compute_instance_template" "condor-compute" { + can_ip_forward = "false" + + disk { + auto_delete = "true" + boot = "true" + device_name = "boot" + disk_size_gb = "200" + mode = "READ_WRITE" + source_image = data.google_compute_image.startimage.self_link + type = "PERSISTENT" + } + + machine_type = var.compute_instance_type + + metadata = { + startup-script = local.compute_startup + } + + name = "${var.cluster_name}-compute" + + network_interface { + access_config { + network_tier = "PREMIUM" + } + + network = "default" + } + + project = var.project + region = var.zone + + scheduling { + automatic_restart = "false" + on_host_maintenance = "TERMINATE" + preemptible = var.use_preemptibles + } + + service_account { + email = var.service_account + # email = "default" + scopes = ["https://www.googleapis.com/auth/monitoring.write", "https://www.googleapis.com/auth/trace.append", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/devstorage.full_control"] + } + + tags = ["${var.cluster_name}-compute"] +} +resource "google_compute_instance_group_manager" "condor-compute-igm" { + base_instance_name = var.cluster_name + name = var.cluster_name + + project = var.project + target_size = "1" + + update_policy { + max_surge_fixed = 5 + minimal_action = "REPLACE" + type = "OPPORTUNISTIC" + } + + version { + instance_template = google_compute_instance_template.condor-compute.self_link + name = "" + } + timeouts { + create = "60m" + delete = "2h" + } + # Yup, didn't want to use this, but I was getting create and destroy errors. + depends_on = [ + google_compute_instance_template.condor-compute + ] + zone = var.zone +} +resource "google_compute_autoscaler" "condor-compute-as" { + autoscaling_policy { + cooldown_period = "60" + max_replicas = var.max_replicas + + metric { + name = "custom.googleapis.com/q0" + target = var.metric_target_queue + type = "GAUGE" + } + metric { + name = "custom.googleapis.com/la0" + target = var.metric_target_loadavg + type = "GAUGE" + } + + min_replicas = var.min_replicas + } + + name = "${var.cluster_name}-compute-as" + project = var.project + target = google_compute_instance_group_manager.condor-compute-igm.self_link + zone = var.zone + timeouts { + create = "60m" + delete = "2h" + } + + depends_on = [ + google_compute_instance_group_manager.condor-compute-igm + ] +} diff --git a/docs/tutorials/multinode/terraform/htcondor/startup-centos-noinstall.sh b/docs/tutorials/multinode/terraform/htcondor/startup-centos-noinstall.sh new file mode 100644 index 00000000..a76774a8 --- /dev/null +++ b/docs/tutorials/multinode/terraform/htcondor/startup-centos-noinstall.sh @@ -0,0 +1,72 @@ +#!/bin/bash -x + +SERVER_TYPE="${htserver_type}" + +############################################################## +# Configure Condor Daemons +############################################################## +cd /tmp +cat < condor_config.local +DISCARD_SESSION_KEYRING_ON_STARTUP=False +CONDOR_ADMIN=${admin_email} +CONDOR_HOST=${cluster_name}-master +EOF + +# Case for compute +if [ "$SERVER_TYPE" == "compute" ]; then +cat <> condor_config.local +# Standard Stuff +DAEMON_LIST = MASTER, STARTD +ALLOW_WRITE = \$(ALLOW_WRITE), \$(CONDOR_HOST) +# Run Dynamics Slots +NUM_SLOTS = 1 +NUM_SLOTS_TYPE_1 = 1 +SLOT_TYPE_1 = 100% +SLOT_TYPE_1_PARTITIONABLE = TRUE +# Allowing Run as Owner +STARTER_ALLOW_RUNAS_OWNER = TRUE +SUBMIT_ATTRS = RunAsOwner +RunAsOwner = True +UID_DOMAIN = c.${project}.internal +TRUST_UID_DOMAIN = True +EOF1 +fi + +# Case for master +if [ "$SERVER_TYPE" == "master" ]; then +cat <> condor_config.local +DAEMON_LIST = MASTER, COLLECTOR, NEGOTIATOR +ALLOW_WRITE = * +EOF2 +fi + +# Case for submit +if [ "$SERVER_TYPE" == "submit" ]; then +cat <> condor_config.local +DAEMON_LIST = MASTER, SCHEDD +ALLOW_WRITE = \$(ALLOW_WRITE), \$(CONDOR_HOST) +# Allowing Run as Owner +STARTER_ALLOW_RUNAS_OWNER = TRUE +SUBMIT_ATTRS = RunAsOwner +RunAsOwner = True +UID_DOMAIN = c.${project}.internal +TRUST_UID_DOMAIN = True +EOF3 +fi + +mkdir -p /etc/condor/config.d +mv condor_config.local /etc/condor/config.d +systemctl start condor;systemctl enable condor + +############################################################## +## Some fluentd Stuff +############################################################## + + +if [ "$SERVER_TYPE" == "submit" ]; then +mkdir -p /var/log/condor/jobs +touch /var/log/condor/jobs/stats.log +chmod 666 /var/log/condor/jobs/stats.log +fi + +service google-fluentd restart diff --git a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh new file mode 100644 index 00000000..7177a285 --- /dev/null +++ b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh @@ -0,0 +1,169 @@ +#!/bin/bash -x + +SERVER_TYPE="${htserver_type}" + +############################################################## +## Install and configure HTCONDOR +############################################################## + +if [ "${condorversion}" == "" ]; then + CONDOR_INSTALL_OPT="condor" +else + CONDOR_INSTALL_OPT="condor-all-${condorversion}" + # email = "487217491196-compute@developer.gserviceaccount.com" +fi +if [ "${osversion}" == "6" ]; then + CONDOR_STARTUP_CMD="service condor start" +else + CONDOR_STARTUP_CMD="systemctl start condor;systemctl enable condor" +fi +CONDOR_REPO_URL=https://research.cs.wisc.edu/htcondor/yum/repo.d/htcondor-stable-rhel${osversion}.repo + +sleep 2 #Give it some time to setup yum +cd /tmp +yum update -y +yum install -y wget curl net-tools vim gcc python3 +wget https://research.cs.wisc.edu/htcondor/yum/RPM-GPG-KEY-HTCondor +rpm --import RPM-GPG-KEY-HTCondor +cd /etc/yum.repos.d && wget $CONDOR_REPO_URL +yum install -y $CONDOR_INSTALL_OPT + +############################################################## +# Install Docker on Compute Nodes +############################################################## +if [ "$SERVER_TYPE" == "compute" ]; then + yum install -y yum-utils + yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo + yum install -y docker-ce docker-ce-cli containerd.io + systemctl start docker + systemctl enable docker + usermod -aG docker condor +fi + +############################################################## +# Configure Condor Daemons +############################################################## +cd /tmp +cat < condor_config.local +DISCARD_SESSION_KEYRING_ON_STARTUP=False +CONDOR_ADMIN=${admin_email} +CONDOR_HOST=${cluster_name}-master +EOF + +# Case for compute +if [ "$SERVER_TYPE" == "compute" ]; then +cat <> condor_config.local +# Standard Stuff +DAEMON_LIST = MASTER, STARTD +ALLOW_WRITE = \$(ALLOW_WRITE), \$(CONDOR_HOST) +# Run Dynamics Slots +NUM_SLOTS = 1 +NUM_SLOTS_TYPE_1 = 1 +SLOT_TYPE_1 = 100% +SLOT_TYPE_1_PARTITIONABLE = TRUE +# Allowing Run as Owner +STARTER_ALLOW_RUNAS_OWNER = TRUE +SUBMIT_ATTRS = RunAsOwner +RunAsOwner = True +UID_DOMAIN = c.${project}.internal +TRUST_UID_DOMAIN = True +HasDocker = True +EOF1 +fi + +# Case for master +if [ "$SERVER_TYPE" == "master" ]; then +cat <> condor_config.local +DAEMON_LIST = MASTER, COLLECTOR, NEGOTIATOR +ALLOW_WRITE = * +EOF2 +fi + +# Case for submit +if [ "$SERVER_TYPE" == "submit" ]; then +cat <> condor_config.local +DAEMON_LIST = MASTER, SCHEDD +ALLOW_WRITE = \$(ALLOW_WRITE), \$(CONDOR_HOST) +# Allowing Run as Owner +STARTER_ALLOW_RUNAS_OWNER = TRUE +SUBMIT_ATTRS = RunAsOwner +RunAsOwner = True +UID_DOMAIN = c.${project}.internal +TRUST_UID_DOMAIN = True +EOF3 +fi + + +mkdir -p /etc/condor/config.d +mv condor_config.local /etc/condor/config.d +eval $CONDOR_STARTUP_CMD + +############################################################## +# Install and configure logging agent for StackDriver +############################################################## +curl -sSO https://dl.google.com/cloudagents/install-logging-agent.sh +bash install-logging-agent.sh + +# Install Custom Metric Plugin: +google-fluentd-gem install fluent-plugin-stackdriver-monitoring + +# Create Fluentd Config + +cat < condor.conf + + @type tail + format none + path /var/log/condor/*Log + pos_file /var/lib/google-fluentd/pos/condor.pos + read_from_head true + tag condor + + + @type exec + command condor_status -direct `hostname` -format "%f " TotalLoadAvg | cut -d " " -f 1 + + keys la0 + + tag condor_la0 + run_interval 5s + + + @type exec + command condor_status -schedd -format "%d" TotalIdleJobs + + keys q0 + + tag condor_q0 + run_interval 5s + + + @type stackdriver_monitoring + project ${project} + + key la0 + type custom.googleapis.com/la0 + metric_kind GAUGE + value_type DOUBLE + + + + @type stackdriver_monitoring + project ${project} + + key q0 + type custom.googleapis.com/q0 + metric_kind GAUGE + value_type INT64 + + +EOF +mkdir -p /etc/google-fluentd/config.d/ +mv condor.conf /etc/google-fluentd/config.d/ + +if [ "$SERVER_TYPE" == "submit" ]; then +mkdir -p /var/log/condor/jobs +touch /var/log/condor/jobs/stats.log +chmod 666 /var/log/condor/jobs/stats.log +fi + +service google-fluentd restart diff --git a/docs/tutorials/multinode/terraform/init.tf b/docs/tutorials/multinode/terraform/init.tf new file mode 100644 index 00000000..2e30961d --- /dev/null +++ b/docs/tutorials/multinode/terraform/init.tf @@ -0,0 +1,3 @@ +provider "google" { + credentials = file("~/.quantum-htcondor.json") +} diff --git a/docs/tutorials/multinode/terraform/main.tf b/docs/tutorials/multinode/terraform/main.tf new file mode 100644 index 00000000..cabf61d2 --- /dev/null +++ b/docs/tutorials/multinode/terraform/main.tf @@ -0,0 +1,19 @@ +variable "cluster_name" { + type = string + default = "c" + description = "Name used to prefix resources in cluster." + +} + +module "htcondor" { + source = "./htcondor/" + cluster_name = var.cluster_name + osversion = "7" + bucket_name = "quantum-hcondor-save" + zone="us-east4-c" + project="quantum-htcondor" + max_replicas=20 + min_replicas=1 + service_account="htcondor@quantum-htcondor.iam.gserviceaccount.com" + use_preemptibles=true +} \ No newline at end of file diff --git a/docs/tutorials/multinode/test.txt b/docs/tutorials/multinode/test.txt new file mode 100644 index 00000000..e69de29b From 2ad573b02b4f4bbad59f44f3068eb8f9d6ff7028 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Wed, 12 May 2021 20:29:15 +0000 Subject: [PATCH 003/246] Added readme --- docs/tutorials/multinode/{test.txt => README.md} | 0 docs/tutorials/multinode/docker.job | 11 +++++++++++ 2 files changed, 11 insertions(+) rename docs/tutorials/multinode/{test.txt => README.md} (100%) create mode 100644 docs/tutorials/multinode/docker.job diff --git a/docs/tutorials/multinode/test.txt b/docs/tutorials/multinode/README.md similarity index 100% rename from docs/tutorials/multinode/test.txt rename to docs/tutorials/multinode/README.md diff --git a/docs/tutorials/multinode/docker.job b/docs/tutorials/multinode/docker.job new file mode 100644 index 00000000..363a233c --- /dev/null +++ b/docs/tutorials/multinode/docker.job @@ -0,0 +1,11 @@ +universe = docker +docker_image = gcr.io/quantum-builds/github.com/quantumlib/qsim +arguments = -c circuit_q24 +transfer_input_files = https://raw.githubusercontent.com/quantumlib/qsim/master/circuits/circuit_q24 +should_transfer_files = YES +when_to_transfer_output = ON_EXIT +output = out/out.$(Process) +error = out/err.$(Process) +log = out/log.$(Process) +request_memory = 30GB +queue 1 From 599f246dfb9f4e7d78ae3a978df8d9e09a4cb795 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Wed, 12 May 2021 20:34:57 +0000 Subject: [PATCH 004/246] removed local creds. --- docs/tutorials/multinode/terraform/init.tf | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/tutorials/multinode/terraform/init.tf b/docs/tutorials/multinode/terraform/init.tf index 2e30961d..8660f9fa 100644 --- a/docs/tutorials/multinode/terraform/init.tf +++ b/docs/tutorials/multinode/terraform/init.tf @@ -1,3 +1,2 @@ provider "google" { - credentials = file("~/.quantum-htcondor.json") } From 2aa5e4af789ac1ddd555323cc6798293dd08f2ac Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Tue, 18 May 2021 15:35:06 +0000 Subject: [PATCH 005/246] Updated job files. --- docs/tutorials/multinode/README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index e69de29b..81dd629b 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -0,0 +1,23 @@ +gcloud iam service-accounts create "htcondor2" --display-name="HTCONDOR 2" +gcloud projects add-iam-policy-binding quantum-htcondor --role roles/iam.serviceAccountUser --member="serviceAccount:htcondor2@quantum-htcondor.iam.gserviceaccount.com" +gcloud projects add-iam-policy-binding quantum-htcondor --role roles/compute.admin --member="serviceAccount:htcondor2@quantum-htcondor.iam.gserviceaccount.com" + + +git clone https://github.com/jrossthomson/qsim.git + +cd qsim/docs/tutorials/multinode/ + +sudo journalctl -f -u google-startup-scripts.service + +condor_q +-- Schedd: c-submit.c.quantum-htcondor.internal : <10.150.15.206:9618?... @ 05/14/21 15:09:57 +OWNER BATCH_NAME SUBMITTED DONE RUN IDLE HOLD TOTAL JOB_IDS + +Total for query: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended +Total for jrossthomson: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended +Total for all users: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended + + +condor_submit .job + +condor_q -better-analyze JobId From 251279a047422cbf013e2762af8e1b6aafe262fd Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Tue, 18 May 2021 15:37:35 +0000 Subject: [PATCH 006/246] update job names --- .../multinode/{docker.job => circuit_q24.job} | 2 +- docs/tutorials/multinode/circuit_q30.job | 11 + .../tutorials/multinode/terraform/circuit_q24 | 6325 ----------------- .../multinode/terraform/htcondor/resources.tf | 19 +- .../htcondor/startup-centos-noinstall.sh | 72 - .../terraform/htcondor/startup-centos.sh | 6 +- docs/tutorials/multinode/terraform/main.tf | 2 +- 7 files changed, 25 insertions(+), 6412 deletions(-) rename docs/tutorials/multinode/{docker.job => circuit_q24.job} (78%) create mode 100644 docs/tutorials/multinode/circuit_q30.job delete mode 100644 docs/tutorials/multinode/terraform/circuit_q24 delete mode 100644 docs/tutorials/multinode/terraform/htcondor/startup-centos-noinstall.sh diff --git a/docs/tutorials/multinode/docker.job b/docs/tutorials/multinode/circuit_q24.job similarity index 78% rename from docs/tutorials/multinode/docker.job rename to docs/tutorials/multinode/circuit_q24.job index 363a233c..5d2639a1 100644 --- a/docs/tutorials/multinode/docker.job +++ b/docs/tutorials/multinode/circuit_q24.job @@ -1,7 +1,7 @@ universe = docker docker_image = gcr.io/quantum-builds/github.com/quantumlib/qsim arguments = -c circuit_q24 -transfer_input_files = https://raw.githubusercontent.com/quantumlib/qsim/master/circuits/circuit_q24 +transfer_input_files = ../../../circuits/circuit_q24 should_transfer_files = YES when_to_transfer_output = ON_EXIT output = out/out.$(Process) diff --git a/docs/tutorials/multinode/circuit_q30.job b/docs/tutorials/multinode/circuit_q30.job new file mode 100644 index 00000000..a3819ecd --- /dev/null +++ b/docs/tutorials/multinode/circuit_q30.job @@ -0,0 +1,11 @@ +universe = docker +docker_image = gcr.io/quantum-builds/github.com/quantumlib/qsim +arguments = -c circuit_q30 +transfer_input_files = ../../../circuits/circuit_q30 +should_transfer_files = YES +when_to_transfer_output = ON_EXIT +output = out/out.$(Process) +error = out/err.$(Process) +log = out/log.$(Process) +request_memory = 340GB +queue 1 diff --git a/docs/tutorials/multinode/terraform/circuit_q24 b/docs/tutorials/multinode/terraform/circuit_q24 deleted file mode 100644 index 757895d7..00000000 --- a/docs/tutorials/multinode/terraform/circuit_q24 +++ /dev/null @@ -1,6325 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - qsim/circuit_q24 at master · quantumlib/qsim · GitHub - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -
- - - -
- - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - -
-
- - - - -
- - - - Permalink - - - -
- -
-
- - - master - - - - -
-
-
- Switch branches/tags - -
- - - -
- -
- -
- - -
- -
- - - - - - - - - - - - - - -
- - -
-
-
-
- -
- -
- - - - Go to file - - -
- - - - - - - - - -
-
-
- - - -
- -
-
- - @sergeisakov - -
- - - - - - -
-
- - Latest commit - 598c0b0 - Feb 7, 2020 - - - - - - History - - -
-
- -
- -
-
- - - 1 - - contributor - - -
- -

- Users who have contributed to this file -

-
- - - - - - -
-
-
-
- - - - - - - - - -
- -
- - -
- - 1258 lines (1258 sloc) - - 12.8 KB -
- - - -
- -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
24
0 h 0
0 h 1
0 h 2
0 h 3
0 h 4
0 h 5
0 h 6
0 h 7
0 h 8
0 h 9
0 h 10
0 h 11
0 h 12
0 h 13
0 h 14
0 h 15
0 h 16
0 h 17
0 h 18
0 h 19
0 h 20
0 h 21
0 h 22
0 h 23
1 cz 0 1
1 cz 4 5
1 cz 8 9
1 cz 12 13
1 cz 16 17
1 cz 20 21
2 cz 2 3
2 cz 6 7
2 cz 10 11
2 cz 14 15
2 cz 18 19
2 cz 22 23
2 t 0
2 t 1
2 t 4
2 t 5
2 t 8
2 t 9
2 t 12
2 t 13
2 t 16
2 t 17
2 t 20
2 t 21
3 cz 6 12
3 cz 8 14
3 cz 10 16
3 t 2
3 t 3
3 t 7
3 t 11
3 t 15
3 t 18
3 t 19
3 t 22
3 t 23
4 cz 7 13
4 cz 9 15
4 cz 11 17
4 t 6
4 x_1_2 8
4 t 10
4 y_1_2 12
4 t 14
4 y_1_2 16
5 cz 1 2
5 cz 9 10
5 cz 13 14
5 cz 21 22
5 x_1_2 7
5 y_1_2 11
5 y_1_2 15
5 y_1_2 17
6 cz 3 4
6 cz 7 8
6 cz 15 16
6 cz 19 20
6 y_1_2 1
6 y_1_2 2
6 y_1_2 9
6 y_1_2 10
6 x_1_2 13
6 x_1_2 14
6 y_1_2 21
6 x_1_2 22
7 cz 0 6
7 cz 2 8
7 cz 4 10
7 cz 13 19
7 cz 15 21
7 cz 17 23
7 x_1_2 3
7 y_1_2 7
7 x_1_2 16
7 x_1_2 20
8 cz 1 7
8 cz 3 9
8 cz 5 11
8 cz 12 18
8 cz 14 20
8 cz 16 22
8 y_1_2 0
8 x_1_2 2
8 y_1_2 4
8 y_1_2 6
8 y_1_2 8
8 x_1_2 10
8 t 13
8 t 15
8 t 17
8 y_1_2 19
8 x_1_2 21
8 y_1_2 23
9 cz 0 1
9 cz 4 5
9 cz 8 9
9 cz 12 13
9 cz 16 17
9 cz 20 21
9 y_1_2 3
9 t 7
9 x_1_2 11
9 t 14
9 y_1_2 18
9 y_1_2 22
10 cz 2 3
10 cz 6 7
10 cz 10 11
10 cz 14 15
10 cz 18 19
10 cz 22 23
10 t 0
10 t 1
10 x_1_2 4
10 x_1_2 5
10 t 8
10 x_1_2 9
10 t 12
10 y_1_2 13
10 t 16
10 y_1_2 17
10 t 20
10 y_1_2 21
11 cz 6 12
11 cz 8 14
11 cz 10 16
11 t 2
11 x_1_2 3
11 y_1_2 7
11 t 11
11 y_1_2 15
11 t 18
11 x_1_2 19
11 t 22
11 x_1_2 23
12 cz 7 13
12 cz 9 15
12 cz 11 17
12 x_1_2 6
12 y_1_2 8
12 t 10
12 x_1_2 12
12 y_1_2 14
12 x_1_2 16
13 cz 1 2
13 cz 9 10
13 cz 13 14
13 cz 21 22
13 t 7
13 x_1_2 11
13 x_1_2 15
13 x_1_2 17
14 cz 3 4
14 cz 7 8
14 cz 15 16
14 cz 19 20
14 x_1_2 1
14 x_1_2 2
14 t 9
14 y_1_2 10
14 x_1_2 13
14 x_1_2 14
14 x_1_2 21
14 y_1_2 22
15 cz 0 6
15 cz 2 8
15 cz 4 10
15 cz 13 19
15 cz 15 21
15 cz 17 23
15 t 3
15 x_1_2 7
15 t 16
15 x_1_2 20
16 cz 1 7
16 cz 3 9
16 cz 5 11
16 cz 12 18
16 cz 14 20
16 cz 16 22
16 x_1_2 0
16 t 2
16 y_1_2 4
16 t 6
16 t 8
16 t 10
16 t 13
16 t 15
16 t 17
16 y_1_2 19
16 t 21
16 t 23
17 cz 0 1
17 cz 4 5
17 cz 8 9
17 cz 12 13
17 cz 16 17
17 cz 20 21
17 x_1_2 3
17 y_1_2 7
17 t 11
17 y_1_2 14
17 x_1_2 18
17 t 22
18 cz 2 3
18 cz 6 7
18 cz 10 11
18 cz 14 15
18 cz 18 19
18 cz 22 23
18 t 0
18 y_1_2 1
18 t 4
18 y_1_2 5
18 x_1_2 8
18 y_1_2 9
18 y_1_2 12
18 x_1_2 13
18 x_1_2 16
18 y_1_2 17
18 t 20
18 x_1_2 21
19 cz 6 12
19 cz 8 14
19 cz 10 16
19 y_1_2 2
19 y_1_2 3
19 x_1_2 7
19 x_1_2 11
19 x_1_2 15
19 y_1_2 18
19 t 19
19 x_1_2 22
19 y_1_2 23
20 cz 7 13
20 cz 9 15
20 cz 11 17
20 x_1_2 6
20 t 8
20 y_1_2 10
20 t 12
20 t 14
20 t 16
21 cz 1 2
21 cz 9 10
21 cz 13 14
21 cz 21 22
21 y_1_2 7
21 t 11
21 t 15
21 t 17
22 cz 3 4
22 cz 7 8
22 cz 15 16
22 cz 19 20
22 t 1
22 x_1_2 2
22 t 9
22 t 10
22 y_1_2 13
22 x_1_2 14
22 t 21
22 y_1_2 22
23 cz 0 6
23 cz 2 8
23 cz 4 10
23 cz 13 19
23 cz 15 21
23 cz 17 23
23 x_1_2 3
23 x_1_2 7
23 x_1_2 16
23 y_1_2 20
24 cz 1 7
24 cz 3 9
24 cz 5 11
24 cz 12 18
24 cz 14 20
24 cz 16 22
24 y_1_2 0
24 y_1_2 2
24 x_1_2 4
24 t 6
24 x_1_2 8
24 y_1_2 10
24 t 13
24 y_1_2 15
24 y_1_2 17
24 x_1_2 19
24 x_1_2 21
24 x_1_2 23
25 cz 0 1
25 cz 4 5
25 cz 8 9
25 cz 12 13
25 cz 16 17
25 cz 20 21
25 t 3
25 y_1_2 7
25 y_1_2 11
25 t 14
25 t 18
25 x_1_2 22
26 cz 2 3
26 cz 6 7
26 cz 10 11
26 cz 14 15
26 cz 18 19
26 cz 22 23
26 t 0
26 x_1_2 1
26 y_1_2 4
26 t 5
26 y_1_2 8
26 y_1_2 9
26 y_1_2 12
26 x_1_2 13
26 y_1_2 16
26 t 17
26 x_1_2 20
26 t 21
27 cz 6 12
27 cz 8 14
27 cz 10 16
27 x_1_2 2
27 y_1_2 3
27 x_1_2 7
27 t 11
27 x_1_2 15
27 y_1_2 18
27 y_1_2 19
27 y_1_2 22
27 y_1_2 23
28 cz 7 13
28 cz 9 15
28 cz 11 17
28 y_1_2 6
28 x_1_2 8
28 t 10
28 x_1_2 12
28 y_1_2 14
28 x_1_2 16
29 cz 1 2
29 cz 9 10
29 cz 13 14
29 cz 21 22
29 y_1_2 7
29 x_1_2 11
29 y_1_2 15
29 x_1_2 17
30 cz 3 4
30 cz 7 8
30 cz 15 16
30 cz 19 20
30 t 1
30 y_1_2 2
30 x_1_2 9
30 y_1_2 10
30 y_1_2 13
30 x_1_2 14
30 x_1_2 21
30 t 22
31 cz 0 6
31 cz 2 8
31 cz 4 10
31 cz 13 19
31 cz 15 21
31 cz 17 23
31 x_1_2 3
31 x_1_2 7
31 t 16
31 y_1_2 20
32 cz 1 7
32 cz 3 9
32 cz 5 11
32 cz 12 18
32 cz 14 20
32 cz 16 22
32 y_1_2 0
32 x_1_2 2
32 x_1_2 4
32 t 6
32 t 8
32 x_1_2 10
32 x_1_2 13
32 x_1_2 15
32 t 17
32 t 19
32 y_1_2 21
32 x_1_2 23
33 cz 0 1
33 cz 4 5
33 cz 8 9
33 cz 12 13
33 cz 16 17
33 cz 20 21
33 y_1_2 3
33 y_1_2 7
33 y_1_2 11
33 t 14
33 x_1_2 18
33 y_1_2 22
34 cz 2 3
34 cz 6 7
34 cz 10 11
34 cz 14 15
34 cz 18 19
34 cz 22 23
34 x_1_2 0
34 x_1_2 1
34 y_1_2 4
34 y_1_2 5
34 y_1_2 8
34 t 9
34 y_1_2 12
34 y_1_2 13
34 y_1_2 16
34 y_1_2 17
34 t 20
34 t 21
35 cz 6 12
35 cz 8 14
35 cz 10 16
35 y_1_2 2
35 x_1_2 3
35 x_1_2 7
35 t 11
35 t 15
35 y_1_2 18
35 y_1_2 19
35 x_1_2 22
35 y_1_2 23
36 cz 7 13
36 cz 9 15
36 cz 11 17
36 y_1_2 6
36 x_1_2 8
36 t 10
36 t 12
36 y_1_2 14
36 t 16
37 cz 1 2
37 cz 9 10
37 cz 13 14
37 cz 21 22
37 y_1_2 7
37 x_1_2 11
37 x_1_2 15
37 t 17
38 cz 3 4
38 cz 7 8
38 cz 15 16
38 cz 19 20
38 t 1
38 t 2
38 x_1_2 9
38 y_1_2 10
38 t 13
38 t 14
38 y_1_2 21
38 y_1_2 22
39 cz 0 6
39 cz 2 8
39 cz 4 10
39 cz 13 19
39 cz 15 21
39 cz 17 23
39 y_1_2 3
39 t 7
39 y_1_2 16
39 x_1_2 20
40 cz 1 7
40 cz 3 9
40 cz 5 11
40 cz 12 18
40 cz 14 20
40 cz 16 22
40 y_1_2 0
40 x_1_2 2
40 t 4
40 t 6
40 y_1_2 8
40 t 10
40 y_1_2 13
40 t 15
40 y_1_2 17
40 t 19
40 x_1_2 21
40 x_1_2 23
41 cz 0 1
41 cz 4 5
41 cz 8 9
41 cz 12 13
41 cz 16 17
41 cz 20 21
41 x_1_2 3
41 y_1_2 7
41 y_1_2 11
41 y_1_2 14
41 x_1_2 18
41 t 22
42 cz 2 3
42 cz 6 7
42 cz 10 11
42 cz 14 15
42 cz 18 19
42 cz 22 23
42 t 0
42 x_1_2 1
42 x_1_2 4
42 x_1_2 5
42 t 8
42 y_1_2 9
42 x_1_2 12
42 t 13
42 t 16
42 t 17
42 t 20
42 y_1_2 21
43 cz 6 12
43 cz 8 14
43 cz 10 16
43 t 2
43 y_1_2 3
43 x_1_2 7
43 x_1_2 11
43 x_1_2 15
43 t 18
43 y_1_2 19
43 y_1_2 22
43 y_1_2 23
44 cz 7 13
44 cz 9 15
44 cz 11 17
44 y_1_2 6
44 x_1_2 8
44 x_1_2 10
44 t 12
44 t 14
44 y_1_2 16
45 cz 1 2
45 cz 9 10
45 cz 13 14
45 cz 21 22
45 t 7
45 t 11
45 t 15
45 x_1_2 17
46 cz 3 4
46 cz 7 8
46 cz 15 16
46 cz 19 20
46 y_1_2 1
46 y_1_2 2
46 t 9
46 t 10
46 y_1_2 13
46 y_1_2 14
46 t 21
46 t 22
47 cz 0 6
47 cz 2 8
47 cz 4 10
47 cz 13 19
47 cz 15 21
47 cz 17 23
47 t 3
47 y_1_2 7
47 t 16
47 x_1_2 20
48 cz 1 7
48 cz 3 9
48 cz 5 11
48 cz 12 18
48 cz 14 20
48 cz 16 22
48 x_1_2 0
48 x_1_2 2
48 y_1_2 4
48 t 6
48 t 8
48 y_1_2 10
48 x_1_2 13
48 y_1_2 15
48 t 17
48 t 19
48 y_1_2 21
48 t 23
49 cz 0 1
49 cz 4 5
49 cz 8 9
49 cz 12 13
49 cz 16 17
49 cz 20 21
49 y_1_2 3
49 t 7
49 x_1_2 11
49 x_1_2 14
49 x_1_2 18
49 x_1_2 22
50 cz 2 3
50 cz 6 7
50 cz 10 11
50 cz 14 15
50 cz 18 19
50 cz 22 23
50 t 0
50 t 1
50 x_1_2 4
50 t 5
50 x_1_2 8
50 x_1_2 9
50 y_1_2 12
50 y_1_2 13
50 y_1_2 16
50 x_1_2 17
50 t 20
50 x_1_2 21
51 cz 6 12
51 cz 8 14
51 cz 10 16
51 y_1_2 2
51 x_1_2 3
51 x_1_2 7
51 t 11
51 t 15
51 t 18
51 x_1_2 19
51 t 22
51 x_1_2 23
52 cz 7 13
52 cz 9 15
52 cz 11 17
52 y_1_2 6
52 y_1_2 8
52 x_1_2 10
52 x_1_2 12
52 y_1_2 14
52 t 16
53 cz 1 2
53 cz 9 10
53 cz 13 14
53 cz 21 22
53 t 7
53 y_1_2 11
53 x_1_2 15
53 t 17
54 cz 3 4
54 cz 7 8
54 cz 15 16
54 cz 19 20
54 x_1_2 1
54 x_1_2 2
54 t 9
54 t 10
54 t 13
54 x_1_2 14
54 t 21
54 y_1_2 22
55 cz 0 6
55 cz 2 8
55 cz 4 10
55 cz 13 19
55 cz 15 21
55 cz 17 23
55 y_1_2 3
55 y_1_2 7
55 x_1_2 16
55 y_1_2 20
56 cz 1 7
56 cz 3 9
56 cz 5 11
56 cz 12 18
56 cz 14 20
56 cz 16 22
56 y_1_2 0
56 y_1_2 2
56 y_1_2 4
56 t 6
56 t 8
56 x_1_2 10
56 y_1_2 13
56 t 15
56 y_1_2 17
56 t 19
56 y_1_2 21
56 t 23
57 cz 0 1
57 cz 4 5
57 cz 8 9
57 cz 12 13
57 cz 16 17
57 cz 20 21
57 x_1_2 3
57 x_1_2 7
57 t 11
57 y_1_2 14
57 y_1_2 18
57 x_1_2 22
58 cz 2 3
58 cz 6 7
58 cz 10 11
58 cz 14 15
58 cz 18 19
58 cz 22 23
58 x_1_2 0
58 t 1
58 t 4
58 y_1_2 5
58 y_1_2 8
58 y_1_2 9
58 y_1_2 12
58 t 13
58 t 16
58 t 17
58 x_1_2 20
58 x_1_2 21
59 cz 6 12
59 cz 8 14
59 cz 10 16
59 t 2
59 t 3
59 y_1_2 7
59 y_1_2 11
59 y_1_2 15
59 t 18
59 x_1_2 19
59 y_1_2 22
59 x_1_2 23
60 cz 7 13
60 cz 9 15
60 cz 11 17
60 y_1_2 6
60 x_1_2 8
60 y_1_2 10
60 x_1_2 12
60 x_1_2 14
60 x_1_2 16
61 cz 1 2
61 cz 9 10
61 cz 13 14
61 cz 21 22
61 x_1_2 7
61 t 11
61 t 15
61 y_1_2 17
62 cz 3 4
62 cz 7 8
62 cz 15 16
62 cz 19 20
62 x_1_2 1
62 x_1_2 2
62 t 9
62 t 10
62 y_1_2 13
62 y_1_2 14
62 t 21
62 x_1_2 22
63 cz 0 6
63 cz 2 8
63 cz 4 10
63 cz 13 19
63 cz 15 21
63 cz 17 23
63 y_1_2 3
63 y_1_2 7
63 t 16
63 t 20
64 cz 1 7
64 cz 3 9
64 cz 5 11
64 cz 12 18
64 cz 14 20
64 cz 16 22
64 t 0
64 y_1_2 2
64 y_1_2 4
64 x_1_2 6
64 y_1_2 8
64 y_1_2 10
64 x_1_2 13
64 y_1_2 15
64 x_1_2 17
64 y_1_2 19
64 y_1_2 21
64 y_1_2 23
65 cz 0 1
65 cz 4 5
65 cz 8 9
65 cz 12 13
65 cz 16 17
65 cz 20 21
65 x_1_2 3
65 x_1_2 7
65 x_1_2 11
65 x_1_2 14
65 x_1_2 18
65 y_1_2 22
66 cz 2 3
66 cz 6 7
66 cz 10 11
66 cz 14 15
66 cz 18 19
66 cz 22 23
66 x_1_2 0
66 y_1_2 1
66 t 4
66 x_1_2 5
66 t 8
66 y_1_2 9
66 y_1_2 12
66 t 13
66 y_1_2 16
66 t 17
66 x_1_2 20
66 x_1_2 21
67 cz 6 12
67 cz 8 14
67 cz 10 16
67 t 2
67 t 3
67 t 7
67 t 11
67 x_1_2 15
67 t 18
67 x_1_2 19
67 t 22
67 x_1_2 23
68 cz 7 13
68 cz 9 15
68 cz 11 17
68 y_1_2 6
68 x_1_2 8
68 x_1_2 10
68 t 12
68 t 14
68 t 16
69 cz 1 2
69 cz 9 10
69 cz 13 14
69 cz 21 22
69 y_1_2 7
69 y_1_2 11
69 y_1_2 15
69 y_1_2 17
70 cz 3 4
70 cz 7 8
70 cz 15 16
70 cz 19 20
70 x_1_2 1
70 y_1_2 2
70 x_1_2 9
70 t 10
70 x_1_2 13
70 y_1_2 14
70 t 21
70 x_1_2 22
71 cz 0 6
71 cz 2 8
71 cz 4 10
71 cz 13 19
71 cz 15 21
71 cz 17 23
71 x_1_2 3
71 x_1_2 7
71 y_1_2 16
71 t 20
72 cz 1 7
72 cz 3 9
72 cz 5 11
72 cz 12 18
72 cz 14 20
72 cz 16 22
72 t 0
72 x_1_2 2
72 x_1_2 4
72 t 6
72 y_1_2 8
72 x_1_2 10
72 t 13
72 x_1_2 15
72 x_1_2 17
72 y_1_2 19
72 y_1_2 21
72 y_1_2 23
73 cz 0 1
73 cz 4 5
73 cz 8 9
73 cz 12 13
73 cz 16 17
73 cz 20 21
73 t 3
73 y_1_2 7
73 t 11
73 x_1_2 14
73 y_1_2 18
73 t 22
74 cz 2 3
74 cz 6 7
74 cz 10 11
74 cz 14 15
74 cz 18 19
74 cz 22 23
74 x_1_2 0
74 y_1_2 1
74 t 4
74 y_1_2 5
74 t 8
74 y_1_2 9
74 y_1_2 12
74 x_1_2 13
74 x_1_2 16
74 y_1_2 17
74 x_1_2 20
74 t 21
75 cz 6 12
75 cz 8 14
75 cz 10 16
75 t 2
75 x_1_2 3
75 t 7
75 x_1_2 11
75 t 15
75 t 18
75 x_1_2 19
75 y_1_2 22
75 x_1_2 23
76 cz 7 13
76 cz 9 15
76 cz 11 17
76 x_1_2 6
76 x_1_2 8
76 y_1_2 10
76 x_1_2 12
76 t 14
76 t 16
77 cz 1 2
77 cz 9 10
77 cz 13 14
77 cz 21 22
77 x_1_2 7
77 y_1_2 11
77 x_1_2 15
77 x_1_2 17
78 cz 3 4
78 cz 7 8
78 cz 15 16
78 cz 19 20
78 t 1
78 y_1_2 2
78 x_1_2 9
78 x_1_2 10
78 t 13
78 x_1_2 14
78 y_1_2 21
78 t 22
79 cz 0 6
79 cz 2 8
79 cz 4 10
79 cz 13 19
79 cz 15 21
79 cz 17 23
79 t 3
79 t 7
79 y_1_2 16
79 t 20
80 cz 1 7
80 cz 3 9
80 cz 5 11
80 cz 12 18
80 cz 14 20
80 cz 16 22
80 t 0
80 t 2
80 y_1_2 4
80 t 6
80 t 8
80 y_1_2 10
80 x_1_2 13
80 t 15
80 t 17
80 t 19
80 t 21
80 y_1_2 23
81 cz 0 1
81 cz 4 5
81 cz 8 9
81 cz 12 13
81 cz 16 17
81 cz 20 21
81 x_1_2 3
81 y_1_2 7
81 x_1_2 11
81 y_1_2 14
81 y_1_2 18
81 x_1_2 22
82 cz 2 3
82 cz 6 7
82 cz 10 11
82 cz 14 15
82 cz 18 19
82 cz 22 23
82 y_1_2 0
82 x_1_2 1
82 t 4
82 x_1_2 5
82 x_1_2 8
82 t 9
82 y_1_2 12
82 y_1_2 13
82 x_1_2 16
82 x_1_2 17
82 x_1_2 20
82 y_1_2 21
83 cz 6 12
83 cz 8 14
83 cz 10 16
83 x_1_2 2
83 t 3
83 x_1_2 7
83 y_1_2 11
83 y_1_2 15
83 t 18
83 y_1_2 19
83 t 22
83 x_1_2 23
84 cz 7 13
84 cz 9 15
84 cz 11 17
84 x_1_2 6
84 t 8
84 x_1_2 10
84 t 12
84 t 14
84 t 16
85 cz 1 2
85 cz 9 10
85 cz 13 14
85 cz 21 22
85 t 7
85 x_1_2 11
85 x_1_2 15
85 t 17
86 cz 3 4
86 cz 7 8
86 cz 15 16
86 cz 19 20
86 y_1_2 1
86 y_1_2 2
86 y_1_2 9
86 t 10
86 t 13
86 x_1_2 14
86 x_1_2 21
86 x_1_2 22
87 cz 0 6
87 cz 2 8
87 cz 4 10
87 cz 13 19
87 cz 15 21
87 cz 17 23
87 y_1_2 3
87 y_1_2 7
87 x_1_2 16
87 t 20
88 cz 1 7
88 cz 3 9
88 cz 5 11
88 cz 12 18
88 cz 14 20
88 cz 16 22
88 x_1_2 0
88 x_1_2 2
88 x_1_2 4
88 t 6
88 y_1_2 8
88 x_1_2 10
88 y_1_2 13
88 y_1_2 15
88 y_1_2 17
88 x_1_2 19
88 y_1_2 21
88 t 23
89 cz 0 1
89 cz 4 5
89 cz 8 9
89 cz 12 13
89 cz 16 17
89 cz 20 21
89 x_1_2 3
89 x_1_2 7
89 y_1_2 11
89 y_1_2 14
89 x_1_2 18
89 t 22
90 cz 2 3
90 cz 6 7
90 cz 10 11
90 cz 14 15
90 cz 18 19
90 cz 22 23
90 t 0
90 t 1
90 t 4
90 t 5
90 t 8
90 t 9
90 y_1_2 12
90 x_1_2 13
90 y_1_2 16
90 x_1_2 17
90 x_1_2 20
90 t 21
91 cz 6 12
91 cz 8 14
91 cz 10 16
91 t 2
91 t 3
91 t 7
91 x_1_2 11
91 t 15
91 y_1_2 18
91 y_1_2 19
91 y_1_2 22
91 y_1_2 23
92 cz 7 13
92 cz 9 15
92 cz 11 17
92 y_1_2 6
92 x_1_2 8
92 t 10
92 t 12
92 t 14
92 x_1_2 16
93 cz 1 2
93 cz 9 10
93 cz 13 14
93 cz 21 22
93 y_1_2 7
93 t 11
93 y_1_2 15
93 t 17
94 cz 3 4
94 cz 7 8
94 cz 15 16
94 cz 19 20
94 y_1_2 1
94 x_1_2 2
94 x_1_2 9
94 y_1_2 10
94 y_1_2 13
94 x_1_2 14
94 x_1_2 21
94 t 22
95 cz 0 6
95 cz 2 8
95 cz 4 10
95 cz 13 19
95 cz 15 21
95 cz 17 23
95 x_1_2 3
95 t 7
95 y_1_2 16
95 t 20
96 cz 1 7
96 cz 3 9
96 cz 5 11
96 cz 12 18
96 cz 14 20
96 cz 16 22
96 x_1_2 0
96 t 2
96 y_1_2 4
96 x_1_2 6
96 t 8
96 x_1_2 10
96 x_1_2 13
96 x_1_2 15
96 y_1_2 17
96 x_1_2 19
96 t 21
96 x_1_2 23
97 cz 0 1
97 cz 4 5
97 cz 8 9
97 cz 12 13
97 cz 16 17
97 cz 20 21
97 t 3
97 y_1_2 7
97 x_1_2 11
97 t 14
97 t 18
97 x_1_2 22
98 cz 2 3
98 cz 6 7
98 cz 10 11
98 cz 14 15
98 cz 18 19
98 cz 22 23
98 t 0
98 t 1
98 t 4
98 y_1_2 5
98 y_1_2 8
98 y_1_2 9
98 x_1_2 12
98 t 13
98 t 16
98 t 17
98 y_1_2 20
98 x_1_2 21
99 cz 6 12
99 cz 8 14
99 cz 10 16
99 y_1_2 2
99 x_1_2 3
99 x_1_2 7
99 t 11
99 y_1_2 15
99 y_1_2 18
99 y_1_2 19
99 t 22
99 t 23
100 cz 7 13
100 cz 9 15
100 cz 11 17
100 y_1_2 6
100 t 8
100 t 10
100 y_1_2 12
100 y_1_2 14
100 x_1_2 16
- - - -
- -
- - - - -
- - -
- - -
-
- - -
- - - -
-
- -
-
- -
- - - - - - - - - - - - - - - - - - - - diff --git a/docs/tutorials/multinode/terraform/htcondor/resources.tf b/docs/tutorials/multinode/terraform/htcondor/resources.tf index c5bf4949..b3f75fe2 100644 --- a/docs/tutorials/multinode/terraform/htcondor/resources.tf +++ b/docs/tutorials/multinode/terraform/htcondor/resources.tf @@ -54,7 +54,7 @@ variable "metric_target_queue" { } variable "compute_instance_type" { type = string - default = "n2-standard-80" + default = "n2-standard-16" } variable "instance_type" { type = string @@ -87,13 +87,13 @@ locals{ "condorversion" = var.condorversion, "admin_email" = var.admin_email }) - master_startup = templatefile( + manager_startup = templatefile( "${path.module}/startup-centos.sh", { "bucket_name" = var.bucket_name, "project" = var.project, "cluster_name" = var.cluster_name, - "htserver_type" = "master", + "htserver_type" = "manager", "osversion" = var.osversion, "condorversion" = var.condorversion, "admin_email" = var.admin_email @@ -103,7 +103,7 @@ data "google_compute_image" "startimage" { family = var.osimage project = var.osproject } -resource "google_compute_instance" "condor-master" { +resource "google_compute_instance" "condor-manager" { boot_disk { auto_delete = "true" device_name = "boot" @@ -122,8 +122,8 @@ resource "google_compute_instance" "condor-master" { enable_display = "false" machine_type = var.instance_type - metadata_startup_script = local.master_startup - name = "${var.cluster_name}-master" + metadata_startup_script = local.manager_startup + name = "${var.cluster_name}-manager" network_interface { access_config { network_tier = "PREMIUM" @@ -154,7 +154,7 @@ resource "google_compute_instance" "condor-master" { enable_vtpm = "true" } - tags = ["${var.cluster_name}-master"] + tags = ["${var.cluster_name}-manager"] zone = var.zone } @@ -257,9 +257,8 @@ resource "google_compute_instance_template" "condor-compute" { } service_account { - email = var.service_account - # email = "default" - scopes = ["https://www.googleapis.com/auth/monitoring.write", "https://www.googleapis.com/auth/trace.append", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/devstorage.full_control"] + email = var.service_account + scopes = ["cloud-platform"] } tags = ["${var.cluster_name}-compute"] diff --git a/docs/tutorials/multinode/terraform/htcondor/startup-centos-noinstall.sh b/docs/tutorials/multinode/terraform/htcondor/startup-centos-noinstall.sh deleted file mode 100644 index a76774a8..00000000 --- a/docs/tutorials/multinode/terraform/htcondor/startup-centos-noinstall.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash -x - -SERVER_TYPE="${htserver_type}" - -############################################################## -# Configure Condor Daemons -############################################################## -cd /tmp -cat < condor_config.local -DISCARD_SESSION_KEYRING_ON_STARTUP=False -CONDOR_ADMIN=${admin_email} -CONDOR_HOST=${cluster_name}-master -EOF - -# Case for compute -if [ "$SERVER_TYPE" == "compute" ]; then -cat <> condor_config.local -# Standard Stuff -DAEMON_LIST = MASTER, STARTD -ALLOW_WRITE = \$(ALLOW_WRITE), \$(CONDOR_HOST) -# Run Dynamics Slots -NUM_SLOTS = 1 -NUM_SLOTS_TYPE_1 = 1 -SLOT_TYPE_1 = 100% -SLOT_TYPE_1_PARTITIONABLE = TRUE -# Allowing Run as Owner -STARTER_ALLOW_RUNAS_OWNER = TRUE -SUBMIT_ATTRS = RunAsOwner -RunAsOwner = True -UID_DOMAIN = c.${project}.internal -TRUST_UID_DOMAIN = True -EOF1 -fi - -# Case for master -if [ "$SERVER_TYPE" == "master" ]; then -cat <> condor_config.local -DAEMON_LIST = MASTER, COLLECTOR, NEGOTIATOR -ALLOW_WRITE = * -EOF2 -fi - -# Case for submit -if [ "$SERVER_TYPE" == "submit" ]; then -cat <> condor_config.local -DAEMON_LIST = MASTER, SCHEDD -ALLOW_WRITE = \$(ALLOW_WRITE), \$(CONDOR_HOST) -# Allowing Run as Owner -STARTER_ALLOW_RUNAS_OWNER = TRUE -SUBMIT_ATTRS = RunAsOwner -RunAsOwner = True -UID_DOMAIN = c.${project}.internal -TRUST_UID_DOMAIN = True -EOF3 -fi - -mkdir -p /etc/condor/config.d -mv condor_config.local /etc/condor/config.d -systemctl start condor;systemctl enable condor - -############################################################## -## Some fluentd Stuff -############################################################## - - -if [ "$SERVER_TYPE" == "submit" ]; then -mkdir -p /var/log/condor/jobs -touch /var/log/condor/jobs/stats.log -chmod 666 /var/log/condor/jobs/stats.log -fi - -service google-fluentd restart diff --git a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh index 7177a285..d883efa1 100644 --- a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh +++ b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh @@ -47,7 +47,7 @@ cd /tmp cat < condor_config.local DISCARD_SESSION_KEYRING_ON_STARTUP=False CONDOR_ADMIN=${admin_email} -CONDOR_HOST=${cluster_name}-master +CONDOR_HOST=${cluster_name}-manager EOF # Case for compute @@ -71,8 +71,8 @@ HasDocker = True EOF1 fi -# Case for master -if [ "$SERVER_TYPE" == "master" ]; then +# Case for manager +if [ "$SERVER_TYPE" == "manager" ]; then cat <> condor_config.local DAEMON_LIST = MASTER, COLLECTOR, NEGOTIATOR ALLOW_WRITE = * diff --git a/docs/tutorials/multinode/terraform/main.tf b/docs/tutorials/multinode/terraform/main.tf index cabf61d2..d532b02c 100644 --- a/docs/tutorials/multinode/terraform/main.tf +++ b/docs/tutorials/multinode/terraform/main.tf @@ -14,6 +14,6 @@ module "htcondor" { project="quantum-htcondor" max_replicas=20 min_replicas=1 - service_account="htcondor@quantum-htcondor.iam.gserviceaccount.com" + service_account="htcondor2@quantum-htcondor.iam.gserviceaccount.com" use_preemptibles=true } \ No newline at end of file From 11207a19f09fbe9c3a5693dadbbb901e6f1b155e Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Wed, 19 May 2021 21:17:28 +0000 Subject: [PATCH 007/246] Update docs, with steps --- docs/tutorials/multinode/terraform/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/tutorials/multinode/terraform/README.md b/docs/tutorials/multinode/terraform/README.md index bbcec480..3669fcfd 100644 --- a/docs/tutorials/multinode/terraform/README.md +++ b/docs/tutorials/multinode/terraform/README.md @@ -39,7 +39,6 @@ module "htcondor" { cluster_name = var.cluster_name osimage = "lsst" osversion = "7" - bucket_name = "[[GCS_FUSE_BUCKET]]" zone="[[YOUR_ZONE]]" project="[[PROJECT_ID]]" max_replicas=20 From 7e457d897fc315993f99c233db08635a2ace0685 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Wed, 19 May 2021 21:18:02 +0000 Subject: [PATCH 008/246] Update docs --- docs/tutorials/multinode/README.md | 77 +++++++++++++++++-- docs/tutorials/multinode/circuit_q24.job | 8 +- docs/tutorials/multinode/circuit_q30.job | 8 +- .../multinode/terraform/htcondor/resources.tf | 40 +++++----- docs/tutorials/multinode/terraform/main.tf | 15 ++-- 5 files changed, 109 insertions(+), 39 deletions(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index 81dd629b..a2fc5a16 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -1,12 +1,79 @@ -gcloud iam service-accounts create "htcondor2" --display-name="HTCONDOR 2" -gcloud projects add-iam-policy-binding quantum-htcondor --role roles/iam.serviceAccountUser --member="serviceAccount:htcondor2@quantum-htcondor.iam.gserviceaccount.com" -gcloud projects add-iam-policy-binding quantum-htcondor --role roles/compute.admin --member="serviceAccount:htcondor2@quantum-htcondor.iam.gserviceaccount.com" +# To run a multinode quantum simulation +s +## Objectives +* Use Terraform to deploy a HTCondor cluster +* Run a multinode simulation using HTCondor +* Query cluster information and monitor running jobs in HTCondor +* Autoscale nodes to runt jobs that new more Simulation capacit -git clone https://github.com/jrossthomson/qsim.git +## Costs -cd qsim/docs/tutorials/multinode/ +This tutorial uses the following billable components of Google Cloud: +* [Compute Engine](https://cloud.google.com/compute/all-pricing) + +To generate a cost estimate based on your projected usage, use the [pricing calculator](https://cloud.google.com/products/calculator). +When you finish this tutorial, you can avoid continued billing by deleting the resources +you created. For more information, see Cleaning up. + +## Before you begin +1. In the Google Cloud Console, on the project selector page, select or create a Google Cloud project. To make this easy, try to choose a project where you are _Owner_ or _Editor_. + + > Note: If you don't plan to keep the resources that you create in this procedure, create a project instead of selecting an existing project. After you finish these steps, you can delete the project, removing all resources associated with the project. +Go to [project selector](https://console.cloud.google.com/projectselector2/home/dashboard) + +1. Make sure that billing is enabled for your Cloud project. Learn how to confirm that billing is enabled for your project. +1. Enable the [Compute Engine and Deployment Manager APIs](https://console.cloud.google.com/flows/enableapi?apiid=compute,deploymentmanager.googleapis.com). +1. In the Cloud Console, [activate Cloud Shell](https://console.cloud.google.com/?cloudshell=true) + +At the bottom of the Cloud Console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Cloud SDK already installed, including the gcloud command-line tool, and with values already set for your current project. It can take a few seconds for the session to initialize. + +## Setting up your environment in Cloud Shell +Select a region and zone for your cluster to run. [This guide can help](https://cloud.google.com/compute/docs/regions-zones). + +Once selected, define the following environment variables in Cloud Shell. + +``` +export TF_VAR_project=[[YOUR_PROJECT_ID]] +export TF_VAR_region=[[YOUR_REGION]] +export TF_VAR_zone=[[YOUR_ZONE]] +``` +With those variables defined, you can configure `gcloud`. Cut and paste the gcloud config commands in your Cloud Shell. + +``` +gcloud config set project $TF_VAR_project +gcloud config set compute/region $TF_VAR_region +gcloud config set compute/zone $TF_VAR_zone +``` +With that completed, you can create a service account that will run the HTCondor cluster. + +``` +gcloud iam service-accounts create htcondor +``` +Then give the +``` +gcloud projects add-iam-policy-binding ${TF_VAR_project} --role roles/iam.serviceAccountUser \ + --member="serviceAccount:htcondor@${TF_VAR_project}.iam.gserviceaccount.com" +gcloud projects add-iam-policy-binding ${TF_VAR_project} --role roles/compute.admin \ +--member="serviceAccount:htcondor@${TF_VAR_project}.iam.gserviceaccount.com" +``` + +## Clone the repository + +The `qsim` repository has all the code required to create the repository and to run the simulations. + +1. Clone the repo and change to the working directory. + ``` + git clone https://github.com/jrossthomson/qsim.git + cd qsim/docs/tutorials/multinode/terraform + ``` + +1. Build the cluster with Terraform + ``` + terraform init + make apply + ``` sudo journalctl -f -u google-startup-scripts.service condor_q diff --git a/docs/tutorials/multinode/circuit_q24.job b/docs/tutorials/multinode/circuit_q24.job index 5d2639a1..a8d6b3ff 100644 --- a/docs/tutorials/multinode/circuit_q24.job +++ b/docs/tutorials/multinode/circuit_q24.job @@ -4,8 +4,8 @@ arguments = -c circuit_q24 transfer_input_files = ../../../circuits/circuit_q24 should_transfer_files = YES when_to_transfer_output = ON_EXIT -output = out/out.$(Process) -error = out/err.$(Process) -log = out/log.$(Process) -request_memory = 30GB +output = out/out.$(Cluster)-$(Process) +error = out/err.$(Cluster)-$(Process) +log = out/log.$(Cluster)-$(Process) +request_memory = 1GB queue 1 diff --git a/docs/tutorials/multinode/circuit_q30.job b/docs/tutorials/multinode/circuit_q30.job index a3819ecd..756295c7 100644 --- a/docs/tutorials/multinode/circuit_q30.job +++ b/docs/tutorials/multinode/circuit_q30.job @@ -4,8 +4,8 @@ arguments = -c circuit_q30 transfer_input_files = ../../../circuits/circuit_q30 should_transfer_files = YES when_to_transfer_output = ON_EXIT -output = out/out.$(Process) -error = out/err.$(Process) -log = out/log.$(Process) -request_memory = 340GB +output = out/out.$(Cluster)-$(Process) +error = out/err.$(Cluster)-$(Process) +log = out/log.$(Cluster)-$(Process) +request_memory = 10GB queue 1 diff --git a/docs/tutorials/multinode/terraform/htcondor/resources.tf b/docs/tutorials/multinode/terraform/htcondor/resources.tf index b3f75fe2..f10d6b2a 100644 --- a/docs/tutorials/multinode/terraform/htcondor/resources.tf +++ b/docs/tutorials/multinode/terraform/htcondor/resources.tf @@ -1,7 +1,3 @@ -variable "bucket_name" { - type = string - default = "" -} variable "cluster_name" { type = string default = "condor" @@ -68,7 +64,6 @@ locals{ compute_startup = templatefile( "${path.module}/startup-centos.sh", { - "bucket_name" = var.bucket_name, "project" = var.project, "cluster_name" = var.cluster_name, "htserver_type" = "compute", @@ -79,7 +74,6 @@ locals{ submit_startup = templatefile( "${path.module}/startup-centos.sh", { - "bucket_name" = var.bucket_name, "project" = var.project, "cluster_name" = var.cluster_name, "htserver_type" = "submit", @@ -90,7 +84,6 @@ locals{ manager_startup = templatefile( "${path.module}/startup-centos.sh", { - "bucket_name" = var.bucket_name, "project" = var.project, "cluster_name" = var.cluster_name, "htserver_type" = "manager", @@ -291,28 +284,33 @@ resource "google_compute_instance_group_manager" "condor-compute-igm" { zone = var.zone } resource "google_compute_autoscaler" "condor-compute-as" { + name = "${var.cluster_name}-compute-as" + project = var.project + target = google_compute_instance_group_manager.condor-compute-igm.self_link + zone = var.zone + autoscaling_policy { cooldown_period = "60" max_replicas = var.max_replicas + min_replicas = var.min_replicas - metric { - name = "custom.googleapis.com/q0" - target = var.metric_target_queue - type = "GAUGE" - } - metric { - name = "custom.googleapis.com/la0" - target = var.metric_target_loadavg - type = "GAUGE" + cpu_utilization { + target = 0.2 } - min_replicas = var.min_replicas + # metric { + # name = "custom.googleapis.com/q0" + # target = var.metric_target_queue + # type = "GAUGE" + # } + # metric { + # name = "custom.googleapis.com/la0" + # target = var.metric_target_loadavg + # type = "GAUGE" + # } + } - name = "${var.cluster_name}-compute-as" - project = var.project - target = google_compute_instance_group_manager.condor-compute-igm.self_link - zone = var.zone timeouts { create = "60m" delete = "2h" diff --git a/docs/tutorials/multinode/terraform/main.tf b/docs/tutorials/multinode/terraform/main.tf index d532b02c..85a3b8b9 100644 --- a/docs/tutorials/multinode/terraform/main.tf +++ b/docs/tutorials/multinode/terraform/main.tf @@ -1,3 +1,9 @@ +variable "project" { + type=string +} +variable "zone" { + type=string +} variable "cluster_name" { type = string default = "c" @@ -8,12 +14,11 @@ variable "cluster_name" { module "htcondor" { source = "./htcondor/" cluster_name = var.cluster_name + project = var.project + zone = var.zone osversion = "7" - bucket_name = "quantum-hcondor-save" - zone="us-east4-c" - project="quantum-htcondor" max_replicas=20 min_replicas=1 - service_account="htcondor2@quantum-htcondor.iam.gserviceaccount.com" - use_preemptibles=true + service_account="htcondor@quantum-htcondor.iam.gserviceaccount.com" + use_preemptibles=false } \ No newline at end of file From 492bf1072d40463dad9b52e733860a58d1b90865 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Wed, 19 May 2021 21:38:43 +0000 Subject: [PATCH 009/246] Fix service account name. --- docs/tutorials/multinode/terraform/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/multinode/terraform/main.tf b/docs/tutorials/multinode/terraform/main.tf index 85a3b8b9..6ddf6cb1 100644 --- a/docs/tutorials/multinode/terraform/main.tf +++ b/docs/tutorials/multinode/terraform/main.tf @@ -19,6 +19,6 @@ module "htcondor" { osversion = "7" max_replicas=20 min_replicas=1 - service_account="htcondor@quantum-htcondor.iam.gserviceaccount.com" + service_account="htcondor@${var.project}.iam.gserviceaccount.com" use_preemptibles=false } \ No newline at end of file From 60ee232d95c23722f0463392f2676fe027edee23 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Thu, 20 May 2021 17:09:23 +0000 Subject: [PATCH 010/246] UPdate to docs. --- docs/tutorials/multinode/README.md | 207 +++++++++++++++++- .../{circuit_q24.job => circuit_q24.sub} | 0 .../{circuit_q30.job => circuit_q30.sub} | 0 docs/tutorials/multinode/out/placeholder | 0 4 files changed, 197 insertions(+), 10 deletions(-) rename docs/tutorials/multinode/{circuit_q24.job => circuit_q24.sub} (100%) rename docs/tutorials/multinode/{circuit_q30.job => circuit_q30.sub} (100%) create mode 100644 docs/tutorials/multinode/out/placeholder diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index a2fc5a16..88c41206 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -51,15 +51,17 @@ With that completed, you can create a service account that will run the HTCondor ``` gcloud iam service-accounts create htcondor ``` -Then give the +Then give the service accounts the permissions that will allow HTCondor to run correctly. ``` gcloud projects add-iam-policy-binding ${TF_VAR_project} --role roles/iam.serviceAccountUser \ --member="serviceAccount:htcondor@${TF_VAR_project}.iam.gserviceaccount.com" gcloud projects add-iam-policy-binding ${TF_VAR_project} --role roles/compute.admin \ --member="serviceAccount:htcondor@${TF_VAR_project}.iam.gserviceaccount.com" +gcloud projects add-iam-policy-binding ${TF_VAR_project} --role roles/monitoring.admin \ +--member="serviceAccount:htcondor@${TF_VAR_project}.iam.gserviceaccount.com" ``` -## Clone the repository +## Clone the repository and build the cluster The `qsim` repository has all the code required to create the repository and to run the simulations. @@ -74,17 +76,202 @@ The `qsim` repository has all the code required to create the repository and to terraform init make apply ``` -sudo journalctl -f -u google-startup-scripts.service +This process will create 3 VMs instances and an autoscaling Managed Instance Group. To see the instances use the glcoud command in the Cloud Shell. +``` +gcloud compute instances list +``` +The output should be something like the output here: +``` +NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS +c-8xlg us-east4-c n2-standard-16 10.150.0.21 34.86.120.132 RUNNING +c-manager us-east4-c n2-standard-4 10.150.0.16 35.188.250.134 RUNNING +c-submit us-east4-c n2-standard-4 10.150.0.17 35.245.189.252 RUNNING +``` + +The Mangaged Instance Group can be seen with gcloud. +``` +gcloud compute instance-groups list +``` +And the output like the following. +``` +NAME LOCATION SCOPE NETWORK MANAGED INSTANCES +c us-east4-c zone default Yes 1 +``` +## Run a job on the cluster +The next step is to run a job on the HTCondor cluster. Jobs are submitted on the `c-submit` VM instance known as the Submit Node in HTCondor. You connect to the Submit Node using gcloud SSH. +``` +gcloud compute ssh c-submit +``` +There will now be a prompt with something like `username@c-submit`. + +For convenience there is prepared HTCondor job submission files in the Github repo. To get these, +clone the repository on the Submit Node, and change to the tutorial directory. +``` + git clone https://github.com/jrossthomson/qsim.git + cd qsim/docs/tutorials/multinode +``` + +It is very likely that the HTCondor installation is not completely finished at this point: it takes several minutes. The `condor_status` command will tell you if the cluster is ready to run jobs. + +``` +condor_status +``` +The output from the command should resemble this. +``` +Name OpSys Arch State Activity LoadAv Mem ActvtyTime + +slot1@c-6klg.c.test-quantum-multinode.internal LINUX X86_64 Unclaimed Idle 0.000 64263 0+00:19:33 + + Machines Owner Claimed Unclaimed Matched Preempting Drain + + X86_64/LINUX 1 0 0 1 0 0 0 + + Total 1 0 0 1 0 0 0 +``` + +If the output shows "Unclaimed" machines, you are ready to submit a job with HTCondor. + +``` +condor_submit circuit_q32.sub +``` +There should be a response indicating the job was submitted. +``` +Submitting job(s). +1 job(s) submitted to cluster 1. +``` +Now you can see if the job is running correctly. +``` condor_q --- Schedd: c-submit.c.quantum-htcondor.internal : <10.150.15.206:9618?... @ 05/14/21 15:09:57 -OWNER BATCH_NAME SUBMITTED DONE RUN IDLE HOLD TOTAL JOB_IDS +``` +The output will be similar to the snippet below. +``` +-- Schedd: c-submit.c.test-quantum-multinode.internal : <10.150.0.6:9618?... @ 05/20/21 15:04:24 +OWNER BATCH_NAME SUBMITTED DONE RUN IDLE TOTAL JOB_IDS +jrossthomson ID: 1 5/20 15:04 _ 1 _ 1 1.0 -Total for query: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended -Total for jrossthomson: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended -Total for all users: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended +Total for query: 1 jobs; 0 completed, 0 removed, 0 idle, 1 running, 0 held, 0 suspended +Total for jrossthomson: 1 jobs; 0 completed, 0 removed, 0 idle, 1 running, 0 held, 0 suspended +Total for all users: 1 jobs; 0 completed, 0 removed, 0 idle, 1 running, 0 held, 0 suspended +``` +When this is completed you should see output in the `out` directory. +``` +ls out/ +err.1-0 log.1-0 out.1-0 +``` +The contents of `out.1-0` will have the content you are expecting from the simulation. This will take about 10 minutes to be complete. +``` +cat out/out.1-0 +000: -4.734056e-05 1.2809795e-05 2.4052194e-09 +001: -3.6258607e-06 2.3642724e-07 1.3202764e-11 +010: -2.9523137e-05 2.280164e-05 1.3915304e-09 +011: -1.3954962e-05 9.4717652e-06 2.8445529e-10 +100: -6.8555892e-06 -6.7632163e-07 4.7456514e-11 +101: -2.0390624e-05 3.1813841e-05 1.4278979e-09 +110: 1.5711608e-05 -7.5214862e-06 3.0342737e-10 +111: 9.2345472e-06 -2.7716227e-05 8.5346613e-10 +``` +## Sumitting many jobs + +The job just submitted only ran a single instance of a qsim simulation. The main purpose of the present study is to +run many (up to thousands of) simulations to provide a broad study with statistical significance. + +A simple way to submit many jobs is to use the `queue` command in the HTCondor. For this step edit the file `circuit_q30.sub` +in your favorite editor, such as vim or nano. You will see the full submission file. +``` +universe = docker/jross + +docker_image = gcr.io/quantum-builds/github.com/quantumlib/qsim +arguments = -c circuit_q30 +transfer_input_files = ../../../circuits/circuit_q30 +should_transfer_files = YES +when_to_transfer_output = ON_EXIT +output = out/out.$(Cluster)-$(Process) +error = out/err.$(Cluster)-$(Process) +log = out/log.$(Cluster)-$(Process) +request_memory = 10GB +queue 1 +``` +The submission file is running the public qsim `docker image` from [QuantumAI](https://quantumai.google/qsim). +The command that is running utimately is running the `qsim` base executable. +``` +qsim_base.x -c circuit_q30 +``` +This is achieved by running the _docker_image_ as listed with the `arguments` "-c circuit_q30". The +circuit file `circuit_q30` is transferred to the compute nodes via the `transfer_input_files` command. + +> If you are interested +> in an introduction to HTCondor in general, there is a +> [great introduction from CERN](https://indico.cern.ch/event/611296/contributions/2604376/attachments/1471164/2276521/TannenbaumT_UserTutorial.pdf). For details on job submission syntax there is a section of the [HTCondor Manual](https://htcondor.readthedocs.io/en/latest/users-manual/submitting-a-job.html) dedicated to this. + +To expand the simulation to run 20 instances of the docker image, the change required is to modify the `queue` command. +``` +queue 20 +``` +Now when you run the `condor_submit` command, +``` +condor_submit circuit_q32.sub +``` +20 jobs will be visible from the `condor_q` command. + +The really cool part is that now when you look at the list of VM instance you see that the system has +automatically scaled up the number of VM machines to support the 20 jobs you have requested to run, or as we normally refer to it, +performs __autoscaling__. + +``` +gcloud compute instances list +NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS +c-2q8w us-east4-c n2-standard-16 10.150.0.15 35.245.204.243 RUNNING +c-40dz us-east4-c n2-standard-16 10.150.0.11 34.86.33.206 RUNNING +c-4x4g us-east4-c n2-standard-16 10.150.0.14 35.245.201.254 RUNNING +c-6klg us-east4-c n2-standard-16 10.150.0.8 34.86.161.51 RUNNING +c-748q us-east4-c n2-standard-16 10.150.0.12 35.245.74.106 RUNNING +c-dgt0 us-east4-c n2-standard-16 10.150.0.13 34.86.102.113 RUNNING +c-manager us-east4-c n2-standard-4 10.150.0.7 35.221.59.182 RUNNING +c-submit us-east4-c n2-standard-4 10.150.0.6 35.245.203.14 RUNNING +c-vdsz us-east4-c n2-standard-16 10.150.0.10 35.236.211.189 RUNNING +c-x4bd us-east4-c n2-standard-16 10.150.0.9 34.86.246.195 RUNNING +``` + +When all these 20 jobs are complete, you can see the output in the `out` directory. +``` +ls out/out.* +out/out.2-0 out/out.2-10 out/out.2-12 out/out.2-14 out/out.2-16 out/out.2-18 out/out.2-2 out/out.2-4 out/out.2-6 out/out.2-8 +out/out.2-1 out/out.2-11 out/out.2-12 out/out.2-15 out/out.2-17 out/out.2-19 out/out.2-2 out/out.2-5 out/out.2-7 out/out.2-9 + +``` +The output of the simulation is in these files. +Finally, after the system has been idle for several minutes, you will see that the number of VM instances with autoscale +back to a single Compute Node, the Manager Node and the Submit node. This helps to control costs by removing VMs when +you do not need them. -condor_submit .job +## Next steps +Further work here will allow you to run multiple simulations for different work. There are several things to look at. + +* Creating and running with your own container +* Running with multiple input files +* Selecting different configurations of the submit file + +## Cleaning up +The easiest way to eliminate billing is to delete the Cloud project you created for the tutorial. Alternatively, +you can delete the individual resources. + +### Delete the project +> __Caution:__ Deleting a project has the following effects: +> Everything in the project is deleted. If you used an existing project for this tutorial, when you delete it, you also delete any other work you've done in the project. +> Custom project IDs are lost. When you created this project, you might have created a custom project ID that you want to use in the future. To preserve the URLs that use the project ID, such as an appspot.com URL, delete selected resources inside the project instead of deleting the whole project. + +1. In the Cloud Console, go to the Manage resources page. + > Go to [Manage resources](https://console.cloud.google.com/iam-admin/projects) + +1. In the project list, select the project that you want to delete, and then click Delete. +1. In the dialog, type the project ID, and then click Shut down to delete the project. + +### Delete the Slurm cluster +The second option is to delete the HTCondor cluster. In the `qsim/docs/tutorials/multinode/terraform` directory, run the make command. +``` + make destroy +``` +This will remove the HTCondor cluster. -condor_q -better-analyze JobId diff --git a/docs/tutorials/multinode/circuit_q24.job b/docs/tutorials/multinode/circuit_q24.sub similarity index 100% rename from docs/tutorials/multinode/circuit_q24.job rename to docs/tutorials/multinode/circuit_q24.sub diff --git a/docs/tutorials/multinode/circuit_q30.job b/docs/tutorials/multinode/circuit_q30.sub similarity index 100% rename from docs/tutorials/multinode/circuit_q30.job rename to docs/tutorials/multinode/circuit_q30.sub diff --git a/docs/tutorials/multinode/out/placeholder b/docs/tutorials/multinode/out/placeholder new file mode 100644 index 00000000..e69de29b From fd7003f3d5ced04a1d40110325539dfcb13ce3e1 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Thu, 20 May 2021 17:17:40 +0000 Subject: [PATCH 011/246] Fixed typo --- docs/tutorials/multinode/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index 88c41206..e960819f 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -1,5 +1,4 @@ -# To run a multinode quantum simulation -s +# Run a multinode quantum simulation ## Objectives * Use Terraform to deploy a HTCondor cluster From aabb5983f26096c0c3093c982cace25dfe9ba25c Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Fri, 21 May 2021 18:14:55 +0000 Subject: [PATCH 012/246] added dentos7 as the os, losing HPC image --- docs/tutorials/multinode/terraform/htcondor/resources.tf | 2 +- docs/tutorials/multinode/terraform/htcondor/startup-centos.sh | 2 +- docs/tutorials/multinode/terraform/main.tf | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/multinode/terraform/htcondor/resources.tf b/docs/tutorials/multinode/terraform/htcondor/resources.tf index f10d6b2a..30c68824 100644 --- a/docs/tutorials/multinode/terraform/htcondor/resources.tf +++ b/docs/tutorials/multinode/terraform/htcondor/resources.tf @@ -16,7 +16,7 @@ variable "osimage" { } variable "osproject" { type = string - default = "click-to-deploy-images" + default = "cloud-hpc-image-public" } variable "condorversion" { type = string diff --git a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh index d883efa1..66220888 100644 --- a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh +++ b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh @@ -22,7 +22,7 @@ CONDOR_REPO_URL=https://research.cs.wisc.edu/htcondor/yum/repo.d/htcondor-stable sleep 2 #Give it some time to setup yum cd /tmp yum update -y -yum install -y wget curl net-tools vim gcc python3 +yum install -y wget curl net-tools vim gcc python3 git wget https://research.cs.wisc.edu/htcondor/yum/RPM-GPG-KEY-HTCondor rpm --import RPM-GPG-KEY-HTCondor cd /etc/yum.repos.d && wget $CONDOR_REPO_URL diff --git a/docs/tutorials/multinode/terraform/main.tf b/docs/tutorials/multinode/terraform/main.tf index 6ddf6cb1..72ac2f8d 100644 --- a/docs/tutorials/multinode/terraform/main.tf +++ b/docs/tutorials/multinode/terraform/main.tf @@ -21,4 +21,6 @@ module "htcondor" { min_replicas=1 service_account="htcondor@${var.project}.iam.gserviceaccount.com" use_preemptibles=false + osproject ="centos-cloud" + osimage ="centos-7" } \ No newline at end of file From f0b7d0e92d29d8bd3c75157bf57f5bbd881621e1 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 20 Jul 2021 10:28:46 -0700 Subject: [PATCH 013/246] Support noise model in simulator construction --- qsimcirq/qsim_simulator.py | 40 ++++++++++++++++++++++++--------- qsimcirq_tests/qsimcirq_test.py | 31 +++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 886334c7..bb7e9e3c 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -16,7 +16,7 @@ from cirq import ( circuits, - linalg, + devices, ops, protocols, sim, @@ -80,7 +80,10 @@ class QSimSimulator( SimulatesExpectationValues, ): def __init__( - self, qsim_options: dict = {}, seed: value.RANDOM_STATE_OR_SEED_LIKE = None + self, + qsim_options: dict = {}, + seed: value.RANDOM_STATE_OR_SEED_LIKE = None, + noise: "devices.NOISE_MODEL_LIKE" = None, ): """Creates a new QSimSimulator using the given options and seed. @@ -109,6 +112,7 @@ def __init__( self._prng = value.parse_random_state(seed) self.qsim_options = {"t": 1, "f": 2, "v": 0, "r": 1} self.qsim_options.update(qsim_options) + self.noise = devices.NoiseModel.from_noise_model_like(noise) def get_seed(self): # Limit seed size to 32-bit integer for C++ conversion. @@ -160,8 +164,12 @@ def _sample_measure_results( ValueError: If there are multiple MeasurementGates with the same key, or if repetitions is negative. """ - if not isinstance(program, qsimc.QSimCircuit): - program = qsimc.QSimCircuit(program, device=program.device) + + # Add noise to the circuit if a noise model was provided. + program = qsimc.QSimCircuit( + self.noise.noisy_moments(program, program.all_qubits()), + device=program.device, + ) # Compute indices of measured qubits ordered_qubits = ops.QubitOrder.DEFAULT.order_for(program.all_qubits()) @@ -277,8 +285,12 @@ def compute_amplitudes_sweep( Returns: List of amplitudes. """ - if not isinstance(program, qsimc.QSimCircuit): - program = qsimc.QSimCircuit(program, device=program.device) + + # Add noise to the circuit if a noise model was provided. + program = qsimc.QSimCircuit( + self.noise.noisy_moments(program, program.all_qubits()), + device=program.device, + ) # qsim numbers qubits in reverse order from cirq cirq_order = ops.QubitOrder.as_qubit_order(qubit_order).order_for( @@ -347,8 +359,12 @@ def simulate_sweep( initial_state = 0 if not isinstance(initial_state, (int, np.ndarray)): raise TypeError("initial_state must be an int or state vector.") - if not isinstance(program, qsimc.QSimCircuit): - program = qsimc.QSimCircuit(program, device=program.device) + + # Add noise to the circuit if a noise model was provided. + program = qsimc.QSimCircuit( + self.noise.noisy_moments(program, program.all_qubits()), + device=program.device, + ) options = {} options.update(self.qsim_options) @@ -476,8 +492,12 @@ def simulate_expectation_values_sweep( initial_state = 0 if not isinstance(initial_state, (int, np.ndarray)): raise TypeError("initial_state must be an int or state vector.") - if not isinstance(program, qsimc.QSimCircuit): - program = qsimc.QSimCircuit(program, device=program.device) + + # Add noise to the circuit if a noise model was provided. + program = qsimc.QSimCircuit( + self.noise.noisy_moments(program, program.all_qubits()), + device=program.device, + ) options = {} options.update(self.qsim_options) diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 7766f017..5bf07c52 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -987,6 +987,37 @@ def test_noise_aggregation(): assert cirq.approx_eq(qsim_evs, expected_evs, atol=0.05) +def test_noise_model(): + q0, q1 = cirq.LineQubit.range(2) + + circuit = cirq.Circuit(cirq.X(q0), cirq.CNOT(q0, q1), cirq.measure(q0, q1, key="m")) + quiet_sim = qsimcirq.QSimSimulator() + quiet_results = quiet_sim.run(circuit, repetitions=100) + assert quiet_results.histogram(key="m")[0b11] == 100 + + class ReadoutError(cirq.NoiseModel): + def noisy_operation(self, operation: "cirq.Operation") -> "cirq.OP_TREE": + if isinstance(operation.gate, cirq.MeasurementGate): + return [cirq.X.on_each(*operation.qubits), operation] + return [operation] + + noisy_sim = qsimcirq.QSimSimulator(noise=ReadoutError()) + noisy_results = noisy_sim.run(circuit, repetitions=100) + # ReadoutError will flip both qubits. + assert noisy_results.histogram(key="m")[0b00] == 100 + + noisy_state = noisy_sim.simulate(circuit) + assert cirq.approx_eq(noisy_state.state_vector(), [1, 0, 0, 0]) + + obs = cirq.Z(q0) + cirq.Z(q1) + noisy_evs = noisy_sim.simulate_expectation_values( + circuit, + observables=obs, + permit_terminal_measurements=True, + ) + assert noisy_evs == [2] + + def test_multi_qubit_fusion(): q0, q1, q2, q3 = cirq.LineQubit.range(4) qubits = [q0, q1, q2, q3] From a52578a07202f65b3d6b1565c14ea6455642efff Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 28 Jul 2021 09:57:46 -0700 Subject: [PATCH 014/246] sorted --- qsimcirq/qsim_simulator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index bb7e9e3c..80196e7b 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -167,7 +167,7 @@ def _sample_measure_results( # Add noise to the circuit if a noise model was provided. program = qsimc.QSimCircuit( - self.noise.noisy_moments(program, program.all_qubits()), + self.noise.noisy_moments(program, sorted(program.all_qubits())), device=program.device, ) @@ -288,7 +288,7 @@ def compute_amplitudes_sweep( # Add noise to the circuit if a noise model was provided. program = qsimc.QSimCircuit( - self.noise.noisy_moments(program, program.all_qubits()), + self.noise.noisy_moments(program, sorted(program.all_qubits())), device=program.device, ) @@ -362,7 +362,7 @@ def simulate_sweep( # Add noise to the circuit if a noise model was provided. program = qsimc.QSimCircuit( - self.noise.noisy_moments(program, program.all_qubits()), + self.noise.noisy_moments(program, sorted(program.all_qubits())), device=program.device, ) @@ -495,7 +495,7 @@ def simulate_expectation_values_sweep( # Add noise to the circuit if a noise model was provided. program = qsimc.QSimCircuit( - self.noise.noisy_moments(program, program.all_qubits()), + self.noise.noisy_moments(program, sorted(program.all_qubits())), device=program.device, ) From ecd1c30e2b61e1b5b89f381e530c260e4a3742b6 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 4 Aug 2021 20:39:35 -0700 Subject: [PATCH 015/246] CpuFactory --- pybind_interface/pybind_main.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 37e6a20e..962e8eac 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -36,8 +36,8 @@ using namespace qsim; namespace { -struct Factory { - Factory(unsigned num_threads) : num_threads(num_threads) {} +struct CpuFactory { + CpuFactory(unsigned num_threads) : num_threads(num_threads) {} using Simulator = qsim::Simulator; using StateSpace = Simulator::StateSpace; @@ -380,6 +380,7 @@ std::vector> qsim_simulate(const py::dict &options) { return {}; } + using Factory = CpuFactory; using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; @@ -427,7 +428,8 @@ std::vector> qtrajectory_simulate(const py::dict &options) { return {}; } - using Simulator = qsim::Simulator; + using Factory = CpuFactory; + using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; @@ -475,6 +477,7 @@ std::vector> qtrajectory_simulate(const py::dict &options) { // Helper class for simulating circuits of all types. class SimulatorHelper { public: + using Factory = CpuFactory; using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; @@ -738,6 +741,7 @@ std::vector qsim_sample(const py::dict &options) { return {}; } + using Factory = CpuFactory; using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; @@ -784,6 +788,7 @@ std::vector qtrajectory_sample(const py::dict &options) { return {}; } + using Factory = CpuFactory; using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; From a1413ac2905b2f6deb2538872092ef9a215f1319 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 4 Aug 2021 21:24:33 -0700 Subject: [PATCH 016/246] Update factory and move to subtypes --- pybind_interface/avx2/pybind_main_avx2.cpp | 20 +++++++ .../avx512/pybind_main_avx512.cpp | 20 +++++++ pybind_interface/basic/pybind_main_basic.cpp | 20 +++++++ pybind_interface/pybind_main.cpp | 60 ++++++++----------- pybind_interface/sse/pybind_main_sse.cpp | 20 +++++++ 5 files changed, 105 insertions(+), 35 deletions(-) diff --git a/pybind_interface/avx2/pybind_main_avx2.cpp b/pybind_interface/avx2/pybind_main_avx2.cpp index 29e5e81f..30a9e6ba 100644 --- a/pybind_interface/avx2/pybind_main_avx2.cpp +++ b/pybind_interface/avx2/pybind_main_avx2.cpp @@ -14,11 +14,31 @@ #include "pybind_main_avx2.h" +#include "../../lib/formux.h" #include "../../lib/simulator_avx.h" namespace qsim { template using Simulator = SimulatorAVX; + + struct Factory { + // num_dblocks is unused, but kept for consistency with GpuFactory. + Factory(unsigned num_threads, unsigned num_dblocks) + : num_threads(num_threads) {} + + using Simulator = qsim::Simulator; + using StateSpace = Simulator::StateSpace; + + StateSpace CreateStateSpace() const { + return StateSpace(num_threads); + } + + Simulator CreateSimulator() const { + return Simulator(num_threads); + } + + unsigned num_threads; + }; } #include "../pybind_main.cpp" diff --git a/pybind_interface/avx512/pybind_main_avx512.cpp b/pybind_interface/avx512/pybind_main_avx512.cpp index 97c50117..298ae2df 100644 --- a/pybind_interface/avx512/pybind_main_avx512.cpp +++ b/pybind_interface/avx512/pybind_main_avx512.cpp @@ -14,11 +14,31 @@ #include "pybind_main_avx512.h" +#include "../../lib/formux.h" #include "../../lib/simulator_avx512.h" namespace qsim { template using Simulator = SimulatorAVX512; + + struct Factory { + // num_dblocks is unused, but kept for consistency with GpuFactory. + Factory(unsigned num_threads, unsigned num_dblocks) + : num_threads(num_threads) {} + + using Simulator = qsim::Simulator; + using StateSpace = Simulator::StateSpace; + + StateSpace CreateStateSpace() const { + return StateSpace(num_threads); + } + + Simulator CreateSimulator() const { + return Simulator(num_threads); + } + + unsigned num_threads; + }; } #include "../pybind_main.cpp" diff --git a/pybind_interface/basic/pybind_main_basic.cpp b/pybind_interface/basic/pybind_main_basic.cpp index afeba392..0644cf04 100644 --- a/pybind_interface/basic/pybind_main_basic.cpp +++ b/pybind_interface/basic/pybind_main_basic.cpp @@ -14,11 +14,31 @@ #include "pybind_main_basic.h" +#include "../../lib/formux.h" #include "../../lib/simulator_basic.h" namespace qsim { template using Simulator = SimulatorBasic; + + struct Factory { + // num_dblocks is unused, but kept for consistency with GpuFactory. + Factory(unsigned num_threads, unsigned num_dblocks) + : num_threads(num_threads) {} + + using Simulator = qsim::Simulator; + using StateSpace = Simulator::StateSpace; + + StateSpace CreateStateSpace() const { + return StateSpace(num_threads); + } + + Simulator CreateSimulator() const { + return Simulator(num_threads); + } + + unsigned num_threads; + }; } #include "../pybind_main.cpp" diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 962e8eac..070c0f45 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -36,23 +36,6 @@ using namespace qsim; namespace { -struct CpuFactory { - CpuFactory(unsigned num_threads) : num_threads(num_threads) {} - - using Simulator = qsim::Simulator; - using StateSpace = Simulator::StateSpace; - - StateSpace CreateStateSpace() const { - return StateSpace(num_threads); - } - - Simulator CreateSimulator() const { - return Simulator(num_threads); - } - - unsigned num_threads; -}; - template T parseOptions(const py::dict &options, const char *key) { if (!options.contains(key)) { @@ -380,7 +363,6 @@ std::vector> qsim_simulate(const py::dict &options) { return {}; } - using Factory = CpuFactory; using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; @@ -401,9 +383,11 @@ std::vector> qsim_simulate(const py::dict &options) { Factory>; unsigned num_threads; + unsigned num_dblocks; Runner::Parameter param; try { num_threads = parseOptions(options, "t\0"); + num_dblocks = 1; param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); param.seed = parseOptions(options, "s\0"); @@ -411,7 +395,7 @@ std::vector> qsim_simulate(const py::dict &options) { IO::errorf(exp.what()); return {}; } - Runner::Run(param, Factory(num_threads), circuit, measure); + Runner::Run(param, Factory(num_threads, num_dblocks), circuit, measure); return amplitudes; } @@ -428,7 +412,6 @@ std::vector> qtrajectory_simulate(const py::dict &options) { return {}; } - using Factory = CpuFactory; using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; @@ -443,10 +426,12 @@ std::vector> qtrajectory_simulate(const py::dict &options) { Runner::Parameter param; unsigned num_threads; + unsigned num_dblocks; uint64_t seed; try { num_threads = parseOptions(options, "t\0"); + num_dblocks = 1; param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); seed = parseOptions(options, "s\0"); @@ -455,8 +440,8 @@ std::vector> qtrajectory_simulate(const py::dict &options) { return {}; } - Simulator simulator = Factory(num_threads).CreateSimulator(); - StateSpace state_space = Factory(num_threads).CreateStateSpace(); + Simulator simulator = Factory(num_threads, num_dblocks).CreateSimulator(); + StateSpace state_space = Factory(num_threads, num_dblocks).CreateStateSpace(); auto measure = [&bitstrings, &ncircuit, &litudes, &state_space]( unsigned k, const State &state, @@ -477,7 +462,6 @@ std::vector> qtrajectory_simulate(const py::dict &options) { // Helper class for simulating circuits of all types. class SimulatorHelper { public: - using Factory = CpuFactory; using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; @@ -538,7 +522,8 @@ class SimulatorHelper { private: SimulatorHelper(const py::dict &options, bool noisy) - : state_space(Factory(1).CreateStateSpace()), state(StateSpace::Null()), + : state_space(Factory(1, 1).CreateStateSpace()), + state(StateSpace::Null()), scratch(StateSpace::Null()) { is_valid = false; is_noisy = noisy; @@ -552,11 +537,12 @@ class SimulatorHelper { num_qubits = circuit.num_qubits; } num_threads = parseOptions(options, "t\0"); + num_dblocks = 1; max_fused_size = parseOptions(options, "f\0"); verbosity = parseOptions(options, "v\0"); seed = parseOptions(options, "s\0"); - state_space = Factory(num_threads).CreateStateSpace(); + state_space = Factory(num_threads, num_dblocks).CreateStateSpace(); state = state_space.Create(num_qubits); is_valid = true; } catch (const std::invalid_argument &exp) { @@ -603,13 +589,14 @@ class SimulatorHelper { std::vector stat; auto params = get_noisy_params(); - Simulator simulator = Factory(num_threads).CreateSimulator(); - StateSpace state_space = Factory(num_threads).CreateStateSpace(); + Simulator simulator = Factory(num_threads, num_dblocks).CreateSimulator(); + StateSpace state_space = Factory(num_threads, num_dblocks).CreateStateSpace(); result = NoisyRunner::RunOnce(params, ncircuit, seed, state_space, simulator, scratch, state, stat); } else { - result = Runner::Run(get_params(), Factory(num_threads), circuit, state); + result = Runner::Run( + get_params(), Factory(num_threads, num_dblocks), circuit, state); } seed += 1; return result; @@ -627,7 +614,7 @@ class SimulatorHelper { std::vector> get_expectation_value( const std::vector>, unsigned>>& opsums_and_qubit_counts) { - Simulator simulator = Factory(num_threads).CreateSimulator(); + Simulator simulator = Factory(num_threads, num_dblocks).CreateSimulator(); using Fuser = MultiQubitGateFuser; std::vector> results; @@ -657,6 +644,7 @@ class SimulatorHelper { unsigned num_qubits; unsigned num_threads; + unsigned num_dblocks; unsigned noisy_reps; unsigned max_fused_size; unsigned verbosity; @@ -741,7 +729,6 @@ std::vector qsim_sample(const py::dict &options) { return {}; } - using Factory = CpuFactory; using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; @@ -750,9 +737,11 @@ std::vector qsim_sample(const py::dict &options) { Factory>; unsigned num_threads; + unsigned num_dblocks; Runner::Parameter param; try { num_threads = parseOptions(options, "t\0"); + num_dblocks = 1; param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); param.seed = parseOptions(options, "s\0"); @@ -762,11 +751,11 @@ std::vector qsim_sample(const py::dict &options) { } std::vector results; - StateSpace state_space = Factory(num_threads).CreateStateSpace(); + StateSpace state_space = Factory(num_threads, num_dblocks).CreateStateSpace(); State state = state_space.Create(circuit.num_qubits); state_space.SetStateZero(state); - if (!Runner::Run(param, Factory(num_threads), circuit, state, results)) { + if (!Runner::Run(param, Factory(num_threads, num_dblocks), circuit, state, results)) { IO::errorf("qsim sampling of the circuit errored out.\n"); return {}; } @@ -788,7 +777,6 @@ std::vector qtrajectory_sample(const py::dict &options) { return {}; } - using Factory = CpuFactory; using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; @@ -798,10 +786,12 @@ std::vector qtrajectory_sample(const py::dict &options) { Runner::Parameter param; unsigned num_threads; + unsigned num_dblocks; uint64_t seed; try { num_threads = parseOptions(options, "t\0"); + num_dblocks = 1; param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); seed = parseOptions(options, "s\0"); @@ -811,8 +801,8 @@ std::vector qtrajectory_sample(const py::dict &options) { return {}; } - Simulator simulator = Factory(num_threads).CreateSimulator(); - StateSpace state_space = Factory(num_threads).CreateStateSpace(); + Simulator simulator = Factory(num_threads, num_dblocks).CreateSimulator(); + StateSpace state_space = Factory(num_threads, num_dblocks).CreateStateSpace(); std::vector> results; diff --git a/pybind_interface/sse/pybind_main_sse.cpp b/pybind_interface/sse/pybind_main_sse.cpp index 0ff8c731..b69b44fc 100644 --- a/pybind_interface/sse/pybind_main_sse.cpp +++ b/pybind_interface/sse/pybind_main_sse.cpp @@ -14,11 +14,31 @@ #include "pybind_main_sse.h" +#include "../../lib/formux.h" #include "../../lib/simulator_sse.h" namespace qsim { template using Simulator = SimulatorSSE; + + struct Factory { + // num_dblocks is unused, but kept for consistency with GpuFactory. + Factory(unsigned num_threads, unsigned num_dblocks) + : num_threads(num_threads) {} + + using Simulator = qsim::Simulator; + using StateSpace = Simulator::StateSpace; + + StateSpace CreateStateSpace() const { + return StateSpace(num_threads); + } + + Simulator CreateSimulator() const { + return Simulator(num_threads); + } + + unsigned num_threads; + }; } #include "../pybind_main.cpp" From e80f363e3cc6a6a5cded3b5476e60ba34068bf90 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 4 Aug 2021 21:39:37 -0700 Subject: [PATCH 017/246] Baseline CUDA test. --- pybind_interface/Makefile | 6 +++ pybind_interface/avx2/pybind_main_avx2.cpp | 2 +- .../avx512/pybind_main_avx512.cpp | 2 +- pybind_interface/basic/pybind_main_basic.cpp | 2 +- pybind_interface/cuda/CMakeLists.txt | 29 +++++++++++++ pybind_interface/cuda/pybind_main_cuda.cu | 43 +++++++++++++++++++ pybind_interface/cuda/pybind_main_cuda.h | 17 ++++++++ pybind_interface/decide/decide.cpp | 8 ++++ pybind_interface/sse/pybind_main_sse.cpp | 2 +- qsimcirq/__init__.py | 10 +++++ qsimcirq_tests/qsimcirq_test.py | 18 ++++++++ 11 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 pybind_interface/cuda/CMakeLists.txt create mode 100644 pybind_interface/cuda/pybind_main_cuda.cu create mode 100644 pybind_interface/cuda/pybind_main_cuda.h diff --git a/pybind_interface/Makefile b/pybind_interface/Makefile index 8e48ff57..a8cdba49 100644 --- a/pybind_interface/Makefile +++ b/pybind_interface/Makefile @@ -3,6 +3,7 @@ QSIMLIB_BASIC = ../qsimcirq/qsim_basic`python3-config --extension-suffix` QSIMLIB_SSE = ../qsimcirq/qsim_sse`python3-config --extension-suffix` QSIMLIB_AVX2 = ../qsimcirq/qsim_avx2`python3-config --extension-suffix` QSIMLIB_AVX512 = ../qsimcirq/qsim_avx512`python3-config --extension-suffix` +QSIMLIB_CUDA = ../qsimcirq/qsim_cuda`python3-config --extension-suffix` QSIMLIB_DECIDE = ../qsimcirq/qsim_decide`python3-config --extension-suffix` @@ -12,12 +13,16 @@ PYBINDFLAGS_SSE = -msse4.1 -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 - PYBINDFLAGS_AVX2 = -mavx2 -mfma -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` PYBINDFLAGS_AVX512 = -mavx512f -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` +# The flags for the compilation of GPU-specific Pybind11 interfaces +PYBINDFLAGS_CUDA = -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` + .PHONY: pybind pybind: $(CXX) basic/pybind_main_basic.cpp -o $(QSIMLIB_BASIC) $(CXXFLAGS) $(PYBINDFLAGS_BASIC) $(CXX) sse/pybind_main_sse.cpp -o $(QSIMLIB_SSE) $(CXXFLAGS) $(PYBINDFLAGS_SSE) $(CXX) avx2/pybind_main_avx2.cpp -o $(QSIMLIB_AVX2) $(CXXFLAGS) $(PYBINDFLAGS_AVX2) $(CXX) avx512/pybind_main_avx512.cpp -o $(QSIMLIB_AVX512) $(CXXFLAGS) $(PYBINDFLAGS_AVX512) + $(NVCC) cuda/pybind_main_cuda.cpp -o $(QSIMLIB_CUDA) $(NVCCFLAGS) -Xcompiler "$(PYBINDFLAGS_CUDA)" $(CXX) decide/decide.cpp -o $(QSIMLIB_DECIDE) $(CXXFLAGS) $(PYBINDFLAGS_BASIC) .PHONY: clean @@ -26,4 +31,5 @@ clean: -rm -f ./sse/*.x ./sse/*.a ./sse/*.so ./sse/*.mod $(QSIMLIB_SSE) -rm -f ./avx2/*.x ./avx2/*.a ./avx2/*.so ./avx2/*.mod $(QSIMLIB_AVX2) -rm -f ./avx512/*.x ./avx512/*.a ./avx512/*.so ./avx512/*.mod $(QSIMLIB_AVX512) + -rm -f ./cuda/*.x ./cuda/*.a ./cuda/*.so ./cuda/*.mod $(QSIMLIB_CUDA) -rm -f ./decide/*.x ./decide/*.a ./decide/*.so ./decide/*.mod $(QSIMLIB_DECIDE) diff --git a/pybind_interface/avx2/pybind_main_avx2.cpp b/pybind_interface/avx2/pybind_main_avx2.cpp index 30a9e6ba..54ad2d30 100644 --- a/pybind_interface/avx2/pybind_main_avx2.cpp +++ b/pybind_interface/avx2/pybind_main_avx2.cpp @@ -22,7 +22,7 @@ namespace qsim { using Simulator = SimulatorAVX; struct Factory { - // num_dblocks is unused, but kept for consistency with GpuFactory. + // num_dblocks is unused, but kept for consistency with GPU version. Factory(unsigned num_threads, unsigned num_dblocks) : num_threads(num_threads) {} diff --git a/pybind_interface/avx512/pybind_main_avx512.cpp b/pybind_interface/avx512/pybind_main_avx512.cpp index 298ae2df..996de2c6 100644 --- a/pybind_interface/avx512/pybind_main_avx512.cpp +++ b/pybind_interface/avx512/pybind_main_avx512.cpp @@ -22,7 +22,7 @@ namespace qsim { using Simulator = SimulatorAVX512; struct Factory { - // num_dblocks is unused, but kept for consistency with GpuFactory. + // num_dblocks is unused, but kept for consistency with GPU version. Factory(unsigned num_threads, unsigned num_dblocks) : num_threads(num_threads) {} diff --git a/pybind_interface/basic/pybind_main_basic.cpp b/pybind_interface/basic/pybind_main_basic.cpp index 0644cf04..244688b5 100644 --- a/pybind_interface/basic/pybind_main_basic.cpp +++ b/pybind_interface/basic/pybind_main_basic.cpp @@ -22,7 +22,7 @@ namespace qsim { using Simulator = SimulatorBasic; struct Factory { - // num_dblocks is unused, but kept for consistency with GpuFactory. + // num_dblocks is unused, but kept for consistency with GPU version. Factory(unsigned num_threads, unsigned num_dblocks) : num_threads(num_threads) {} diff --git a/pybind_interface/cuda/CMakeLists.txt b/pybind_interface/cuda/CMakeLists.txt new file mode 100644 index 00000000..75e23fbb --- /dev/null +++ b/pybind_interface/cuda/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.11) +project(qsim) + +IF (WIN32) + set(CMAKE_CXX_FLAGS "/O2 /openmp") +ELSE() + set(CMAKE_CXX_FLAGS "-O3 -fopenmp") +ENDIF() + + +if(APPLE) + set(CMAKE_CXX_STANDARD 14) + include_directories("/usr/local/include" "/usr/local/opt/llvm/include") + link_directories("/usr/local/lib" "/usr/local/opt/llvm/lib") +endif() + +include(FetchContent) + +FetchContent_Declare( + pybind11 + GIT_REPOSITORY https://github.com/pybind/pybind11 + GIT_TAG v2.2.4 +) +FetchContent_GetProperties(pybind11) +if(NOT pybind11_POPULATED) + FetchContent_Populate(pybind11) + add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) +endif() +pybind11_add_module(qsim_cuda pybind_main_cuda.cpp) diff --git a/pybind_interface/cuda/pybind_main_cuda.cu b/pybind_interface/cuda/pybind_main_cuda.cu new file mode 100644 index 00000000..4fec9761 --- /dev/null +++ b/pybind_interface/cuda/pybind_main_cuda.cu @@ -0,0 +1,43 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pybind_main_cuda.h" + +#include "../../lib/simulator_cuda.h" + +namespace qsim { + template + using Simulator = SimulatorCUDA; + + struct Factory { + using Simulator = qsim::Simulator; + using StateSpace = Simulator::StateSpace; + + Factory(unsigned num_threads, unsigned num_dblocks) + : ss_params(num_threads, num_dblocks), sim_params(num_threads) {} + + StateSpace CreateStateSpace() const { + return StateSpace(ss_params); + } + + Simulator CreateSimulator() const { + return Simulator(sim_params); + } + + const StateSpace::Parameter ss_params; + const Simulator::Parameter sim_params; + }; +} + +#include "../pybind_main.cpp" diff --git a/pybind_interface/cuda/pybind_main_cuda.h b/pybind_interface/cuda/pybind_main_cuda.h new file mode 100644 index 00000000..f6f463f5 --- /dev/null +++ b/pybind_interface/cuda/pybind_main_cuda.h @@ -0,0 +1,17 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "../pybind_main.h" + +PYBIND11_MODULE(qsim_basic, m) { MODULE_BINDINGS } diff --git a/pybind_interface/decide/decide.cpp b/pybind_interface/decide/decide.cpp index 2fb41848..6b8b8776 100644 --- a/pybind_interface/decide/decide.cpp +++ b/pybind_interface/decide/decide.cpp @@ -45,6 +45,14 @@ int detect_instructions() { return static_cast(instr); } +enum GPUCapabilities { CUDA = 0, NO_GPU = 10 }; + +int detect_gpu() { + // TODO: detect non-CUDA + GPUCapabilities gpu = CUDA; + return gpu; +} + PYBIND11_MODULE(qsim_decide, m) { m.doc() = "pybind11 plugin"; // optional module docstring diff --git a/pybind_interface/sse/pybind_main_sse.cpp b/pybind_interface/sse/pybind_main_sse.cpp index b69b44fc..21490f68 100644 --- a/pybind_interface/sse/pybind_main_sse.cpp +++ b/pybind_interface/sse/pybind_main_sse.cpp @@ -22,7 +22,7 @@ namespace qsim { using Simulator = SimulatorSSE; struct Factory { - // num_dblocks is unused, but kept for consistency with GpuFactory. + // num_dblocks is unused, but kept for consistency with GPU version. Factory(unsigned num_threads, unsigned num_dblocks) : num_threads(num_threads) {} diff --git a/qsimcirq/__init__.py b/qsimcirq/__init__.py index d0518841..687d899a 100644 --- a/qsimcirq/__init__.py +++ b/qsimcirq/__init__.py @@ -15,7 +15,17 @@ def _load_simd_qsim(): return qsim +def _load_qsim_gpu(): + instr = qsim_decide.detect_gpu() + if instr == 0: + qsim_gpu = importlib.import_module("qsimcirq.qsim_cuda") + else: + qsim_gpu = None + return qsim_gpu + + qsim = _load_simd_qsim() +qsim_gpu = _load_qsim_gpu() from .qsim_circuit import add_op_to_opstring, add_op_to_circuit, QSimCircuit from .qsim_simulator import QSimSimulatorState, QSimSimulatorTrialResult, QSimSimulator diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 144fce5f..ba72935f 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -1062,6 +1062,24 @@ def test_cirq_qsimh_simulate(): assert np.allclose(result, [0j, 0j, (1 + 0j), 0j]) +def test_cirq_qsim_gpu_simulate(): + # Dummy test. + print(qsimcirq.qsim_gpu) + assert False + # # Pick qubits. + # a, b = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] + + # # Create a circuit + # cirq_circuit = cirq.Circuit(cirq.CNOT(a, b), cirq.CNOT(b, a), cirq.X(a)) + + # qsimh_options = {"k": [0], "w": 0, "p": 1, "r": 1} + # qsimhSim = qsimcirq.QSimhSimulator(qsimh_options) + # result = qsimhSim.compute_amplitudes( + # cirq_circuit, bitstrings=[0b00, 0b01, 0b10, 0b11] + # ) + # assert np.allclose(result, [0j, 0j, (1 + 0j), 0j]) + + def test_cirq_qsim_params(): qubit = cirq.GridQubit(0, 0) From 2a0a1a688fa7b03d83f33e8aa98f639da60b4fe4 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 5 Aug 2021 07:53:17 -0700 Subject: [PATCH 018/246] Test patches --- .../cuda/{pybind_main_cuda.cu => pybind_main_cuda.cpp} | 0 pybind_interface/cuda/pybind_main_cuda.h | 2 +- pybind_interface/decide/decide.cpp | 3 +++ 3 files changed, 4 insertions(+), 1 deletion(-) rename pybind_interface/cuda/{pybind_main_cuda.cu => pybind_main_cuda.cpp} (100%) diff --git a/pybind_interface/cuda/pybind_main_cuda.cu b/pybind_interface/cuda/pybind_main_cuda.cpp similarity index 100% rename from pybind_interface/cuda/pybind_main_cuda.cu rename to pybind_interface/cuda/pybind_main_cuda.cpp diff --git a/pybind_interface/cuda/pybind_main_cuda.h b/pybind_interface/cuda/pybind_main_cuda.h index f6f463f5..d7b2a2a2 100644 --- a/pybind_interface/cuda/pybind_main_cuda.h +++ b/pybind_interface/cuda/pybind_main_cuda.h @@ -14,4 +14,4 @@ #include "../pybind_main.h" -PYBIND11_MODULE(qsim_basic, m) { MODULE_BINDINGS } +PYBIND11_MODULE(qsim_cuda, m) { MODULE_BINDINGS } diff --git a/pybind_interface/decide/decide.cpp b/pybind_interface/decide/decide.cpp index 6b8b8776..1355b11c 100644 --- a/pybind_interface/decide/decide.cpp +++ b/pybind_interface/decide/decide.cpp @@ -58,4 +58,7 @@ PYBIND11_MODULE(qsim_decide, m) { // Methods for returning amplitudes m.def("detect_instructions", &detect_instructions, "Detect SIMD"); + + // Detect available GPUs. + m.def("detect_gpu", &detect_gpu, "Detect GPU"); } From 2f3a585afcab57a9e1098484d1288594c8343763 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 5 Aug 2021 10:35:28 -0700 Subject: [PATCH 019/246] Minor issues. --- pybind_interface/Makefile | 4 ++-- pybind_interface/cuda/pybind_main_cuda.cpp | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pybind_interface/Makefile b/pybind_interface/Makefile index a8cdba49..9c9bb6a9 100644 --- a/pybind_interface/Makefile +++ b/pybind_interface/Makefile @@ -11,7 +11,7 @@ QSIMLIB_DECIDE = ../qsimcirq/qsim_decide`python3-config --extension-suffix` PYBINDFLAGS_BASIC = -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` PYBINDFLAGS_SSE = -msse4.1 -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` PYBINDFLAGS_AVX2 = -mavx2 -mfma -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` -PYBINDFLAGS_AVX512 = -mavx512f -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` +PYBINDFLAGS_AVX512 = -std=c++17 -Xcompiler "-mavx512f -Wall -shared -fPIC `python3 -m pybind11 --includes`" # The flags for the compilation of GPU-specific Pybind11 interfaces PYBINDFLAGS_CUDA = -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` @@ -22,7 +22,7 @@ pybind: $(CXX) sse/pybind_main_sse.cpp -o $(QSIMLIB_SSE) $(CXXFLAGS) $(PYBINDFLAGS_SSE) $(CXX) avx2/pybind_main_avx2.cpp -o $(QSIMLIB_AVX2) $(CXXFLAGS) $(PYBINDFLAGS_AVX2) $(CXX) avx512/pybind_main_avx512.cpp -o $(QSIMLIB_AVX512) $(CXXFLAGS) $(PYBINDFLAGS_AVX512) - $(NVCC) cuda/pybind_main_cuda.cpp -o $(QSIMLIB_CUDA) $(NVCCFLAGS) -Xcompiler "$(PYBINDFLAGS_CUDA)" + $(NVCC) cuda/pybind_main_cuda.cpp -o $(QSIMLIB_CUDA) $(NVCCFLAGS) $(PYBINDFLAGS_CUDA) $(CXX) decide/decide.cpp -o $(QSIMLIB_DECIDE) $(CXXFLAGS) $(PYBINDFLAGS_BASIC) .PHONY: clean diff --git a/pybind_interface/cuda/pybind_main_cuda.cpp b/pybind_interface/cuda/pybind_main_cuda.cpp index 4fec9761..b55d0d65 100644 --- a/pybind_interface/cuda/pybind_main_cuda.cpp +++ b/pybind_interface/cuda/pybind_main_cuda.cpp @@ -17,7 +17,6 @@ #include "../../lib/simulator_cuda.h" namespace qsim { - template using Simulator = SimulatorCUDA; struct Factory { @@ -25,7 +24,7 @@ namespace qsim { using StateSpace = Simulator::StateSpace; Factory(unsigned num_threads, unsigned num_dblocks) - : ss_params(num_threads, num_dblocks), sim_params(num_threads) {} + : ss_params{num_threads, num_dblocks}, sim_params{num_threads} {} StateSpace CreateStateSpace() const { return StateSpace(ss_params); From 7d6d0cb70c102bc9e5a2f8260d41f1dd6c62774f Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 5 Aug 2021 10:46:59 -0700 Subject: [PATCH 020/246] Xcompiler is for CUDA --- pybind_interface/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pybind_interface/Makefile b/pybind_interface/Makefile index 9c9bb6a9..cbce2141 100644 --- a/pybind_interface/Makefile +++ b/pybind_interface/Makefile @@ -11,10 +11,10 @@ QSIMLIB_DECIDE = ../qsimcirq/qsim_decide`python3-config --extension-suffix` PYBINDFLAGS_BASIC = -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` PYBINDFLAGS_SSE = -msse4.1 -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` PYBINDFLAGS_AVX2 = -mavx2 -mfma -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` -PYBINDFLAGS_AVX512 = -std=c++17 -Xcompiler "-mavx512f -Wall -shared -fPIC `python3 -m pybind11 --includes`" +PYBINDFLAGS_AVX512 = -mavx512f -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` # The flags for the compilation of GPU-specific Pybind11 interfaces -PYBINDFLAGS_CUDA = -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` +PYBINDFLAGS_CUDA = -std=c++17 -Xcompiler "-Wall -shared -fPIC `python3 -m pybind11 --includes`" .PHONY: pybind pybind: From bbe5b583a4bcbf3cfc4c44368ca3f0e8ea765051 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 6 Aug 2021 09:14:19 -0700 Subject: [PATCH 021/246] Compile CUDA as CUDA --- pybind_interface/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybind_interface/Makefile b/pybind_interface/Makefile index cbce2141..89ea0607 100644 --- a/pybind_interface/Makefile +++ b/pybind_interface/Makefile @@ -14,7 +14,7 @@ PYBINDFLAGS_AVX2 = -mavx2 -mfma -Wall -shared -std=c++17 -fPIC `python3 -m pybin PYBINDFLAGS_AVX512 = -mavx512f -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` # The flags for the compilation of GPU-specific Pybind11 interfaces -PYBINDFLAGS_CUDA = -std=c++17 -Xcompiler "-Wall -shared -fPIC `python3 -m pybind11 --includes`" +PYBINDFLAGS_CUDA = -std=c++17 -x cu -Xcompiler "-Wall -shared -fPIC `python3 -m pybind11 --includes`" .PHONY: pybind pybind: From 173171a32575ddb7e274293d2cbfc3ba11c99b29 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 6 Aug 2021 09:34:01 -0700 Subject: [PATCH 022/246] Split GPU module bindings. --- pybind_interface/cuda/pybind_main_cuda.h | 2 +- pybind_interface/pybind_main.h | 93 ++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/pybind_interface/cuda/pybind_main_cuda.h b/pybind_interface/cuda/pybind_main_cuda.h index d7b2a2a2..b5b5979f 100644 --- a/pybind_interface/cuda/pybind_main_cuda.h +++ b/pybind_interface/cuda/pybind_main_cuda.h @@ -14,4 +14,4 @@ #include "../pybind_main.h" -PYBIND11_MODULE(qsim_cuda, m) { MODULE_BINDINGS } +PYBIND11_MODULE(qsim_cuda, m) { GPU_MODULE_BINDINGS } diff --git a/pybind_interface/pybind_main.h b/pybind_interface/pybind_main.h index 8de3d455..c1ce850e 100644 --- a/pybind_interface/pybind_main.h +++ b/pybind_interface/pybind_main.h @@ -293,4 +293,97 @@ std::vector> qsimh_simulate(const py::dict &options); \ m.def("add_gate_to_opstring", &add_gate_to_opstring, \ "Adds a gate to the given opstring."); + +#define GPU_MODULE_BINDINGS \ + m.doc() = "pybind11 plugin"; /* optional module docstring */ \ + /* Methods for returning amplitudes */ \ + m.def("qsim_simulate", &qsim_simulate, "Call the qsim simulator"); \ + m.def("qtrajectory_simulate", &qtrajectory_simulate, \ + "Call the qtrajectory simulator"); \ + \ + /* Methods for returning full state */ \ + m.def("qsim_simulate_fullstate", \ + static_cast(*)(const py::dict&, uint64_t)>( \ + &qsim_simulate_fullstate), \ + "Call the qsim simulator for full state vector simulation"); \ + m.def("qsim_simulate_fullstate", \ + static_cast(*)(const py::dict&, \ + const py::array_t&)>( \ + &qsim_simulate_fullstate), \ + "Call the qsim simulator for full state vector simulation"); \ + \ + m.def("qtrajectory_simulate_fullstate", \ + static_cast(*)(const py::dict&, uint64_t)>( \ + &qtrajectory_simulate_fullstate), \ + "Call the qtrajectory simulator for full state vector simulation"); \ + m.def("qtrajectory_simulate_fullstate", \ + static_cast(*)(const py::dict&, \ + const py::array_t&)>( \ + &qtrajectory_simulate_fullstate), \ + "Call the qtrajectory simulator for full state vector simulation"); \ + \ + /* Methods for returning samples */ \ + m.def("qsim_sample", &qsim_sample, "Call the qsim sampler"); \ + m.def("qtrajectory_sample", &qtrajectory_sample, \ + "Call the qtrajectory sampler"); \ + \ + using GateCirq = qsim::Cirq::GateCirq; \ + using OpString = qsim::OpString; \ + \ + /* Methods for returning expectation values */ \ + m.def("qsim_simulate_expectation_values", \ + static_cast>(*)( \ + const py::dict&, \ + const std::vector, unsigned>>&, \ + uint64_t)>( \ + &qsim_simulate_expectation_values), \ + "Call the qsim simulator for expectation value simulation"); \ + m.def("qsim_simulate_expectation_values", \ + static_cast>(*)( \ + const py::dict&, \ + const std::vector, unsigned>>&, \ + const py::array_t&)>( \ + &qsim_simulate_expectation_values), \ + "Call the qsim simulator for expectation value simulation"); \ + \ + m.def("qtrajectory_simulate_expectation_values", \ + static_cast>(*)( \ + const py::dict&, \ + const std::vector, unsigned>>&, \ + uint64_t)>( \ + &qtrajectory_simulate_expectation_values), \ + "Call the qtrajectory simulator for expectation value simulation"); \ + m.def("qtrajectory_simulate_expectation_values", \ + static_cast>(*)( \ + const py::dict&, \ + const std::vector, unsigned>>&, \ + const py::array_t&)>( \ + &qtrajectory_simulate_expectation_values), \ + "Call the qtrajectory simulator for expectation value simulation"); \ + \ + /* Method for hybrid simulation */ \ + m.def("qsimh_simulate", &qsimh_simulate, "Call the qsimh simulator"); \ + \ + m.def("add_gate", &add_gate, "Adds a gate to the given circuit."); \ + m.def("add_diagonal_gate", &add_diagonal_gate, \ + "Adds a two- or three-qubit diagonal gate to the given circuit."); \ + m.def("add_matrix_gate", &add_matrix_gate, \ + "Adds a matrix-defined gate to the given circuit."); \ + m.def("control_last_gate", &control_last_gate, \ + "Applies controls to the final gate of a circuit."); \ + \ + m.def("add_gate_channel", &add_gate_channel, \ + "Adds a gate to the given noisy circuit."); \ + m.def("add_diagonal_gate_channel", &add_diagonal_gate_channel, \ + "Adds a two- or three-qubit diagonal gate to the given noisy circuit."); \ + m.def("add_matrix_gate_channel", &add_matrix_gate_channel, \ + "Adds a matrix-defined gate to the given noisy circuit."); \ + m.def("control_last_gate_channel", &control_last_gate_channel, \ + "Applies controls to the final channel of a noisy circuit."); \ + \ + m.def("add_channel", &add_channel, \ + "Adds a channel to the given noisy circuit."); \ + \ + m.def("add_gate_to_opstring", &add_gate_to_opstring, \ + "Adds a gate to the given opstring."); #endif From d67f8322ded9ce4befdbda02a8b2371690d4de9f Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Fri, 6 Aug 2021 17:47:17 +0000 Subject: [PATCH 023/246] Working on autoscaling. --- docs/tutorials/multinode/README.md | 7 +- .../multinode/terraform/autoscaler.py | 275 ++++++++++++++++++ .../multinode/terraform/htcondor/noise.py | 24 ++ .../multinode/terraform/htcondor/noise.sub | 12 + .../multinode/terraform/htcondor/resources.tf | 8 +- 5 files changed, 320 insertions(+), 6 deletions(-) create mode 100644 docs/tutorials/multinode/terraform/autoscaler.py create mode 100644 docs/tutorials/multinode/terraform/htcondor/noise.py create mode 100644 docs/tutorials/multinode/terraform/htcondor/noise.sub diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index e960819f..cbe78f00 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -16,6 +16,9 @@ To generate a cost estimate based on your projected usage, use the [pricing calc When you finish this tutorial, you can avoid continued billing by deleting the resources you created. For more information, see Cleaning up. +> Note: the resources created in this tutorial will persist until deleted or the project is deleted. Google Cloud will charge for this time. + + ## Before you begin 1. In the Google Cloud Console, on the project selector page, select or create a Google Cloud project. To make this easy, try to choose a project where you are _Owner_ or _Editor_. @@ -132,7 +135,7 @@ slot1@c-6klg.c.test-quantum-multinode.internal LINUX X86_64 Unclaimed Idle If the output shows "Unclaimed" machines, you are ready to submit a job with HTCondor. ``` -condor_submit circuit_q32.sub +condor_submit circuit_q30.sub ``` There should be a response indicating the job was submitted. ``` @@ -209,7 +212,7 @@ queue 20 ``` Now when you run the `condor_submit` command, ``` -condor_submit circuit_q32.sub +condor_submit circuit_q30.sub ``` 20 jobs will be visible from the `condor_q` command. diff --git a/docs/tutorials/multinode/terraform/autoscaler.py b/docs/tutorials/multinode/terraform/autoscaler.py new file mode 100644 index 00000000..af68cf45 --- /dev/null +++ b/docs/tutorials/multinode/terraform/autoscaler.py @@ -0,0 +1,275 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright 2018 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Script for resizing managed instance group (MIG) cluster size based +# on the number of jobs in the Condor Queue. + +from pprint import pprint +from googleapiclient import discovery +from oauth2client.client import GoogleCredentials + +import os +import math +import argparse + +parser = argparse.ArgumentParser("autoscaler.py") +parser.add_argument("-p", "--project_id", help="Project id", type=str) +parser.add_argument("-r", "--region", help="GCP region where the managed instance group is located", type=str) +parser.add_argument("-z", "--zone", help="Name of GCP zone where the managed instance group is located", type=str) +parser.add_argument("-g", "--group_manager", help="Name of the managed instance group", type=str) +parser.add_argument("-c", "--computeinstancelimit", help="Maximum number of compute instances", type=int) +parser.add_argument("-v", "--verbosity", help="Increase output verbosity. 1-show basic debug info. 2-show detail debug info", type=int, choices=[0, 1, 2]) +args = parser.parse_args() + +# Project ID +project = args.project_id # Ex:'slurm-var-demo' + +# Region where the managed instance group is located +region = args.region # Ex: 'us-central1' + +# Name of the zone where the managed instance group is located +zone = args.zone # Ex: 'us-central1-f' + +# The name of the managed instance group. +instance_group_manager = args.group_manager # Ex: 'condor-compute-igm' + +# Default number of cores per intance, will be replaced with actual value +cores_per_node = 4 + +# Default number of running instances that the managed instance group should maintain at any given time. This number will go up and down based on the load (number of jobs in the queue) +size = 0 + +# Debug level: 1-print debug information, 2 - print detail debug information +debug = 0 +if (args.verbosity): + debug = args.verbosity + +# Limit for the maximum number of compute instance. If zero (default setting), no limit will be enforced by the script +compute_instance_limit = 0 +if (args.computeinstancelimit): + compute_instance_limit = abs(args.computeinstancelimit) + + +if debug > 1: + print('Launching autoscaler.py with the following arguments:') + print('project_id: ' + project) + print('region: ' + region) + print('zone: ' + zone) + print('group_manager: ' + instance_group_manager) + print('computeinstancelimit: ' + str(compute_instance_limit)) + print('debuglevel: ' + str(debug)) + + +# Remove specified instance from MIG and decrease MIG size +def deleteFromMig(instance): + instanceUrl = 'https://www.googleapis.com/compute/v1/projects/' \ + + project + '/zones/' + zone + '/instances/' + instance + instances_to_delete = {'instances': [instanceUrl]} + + requestDelInstance = \ + service.instanceGroupManagers().deleteInstances(project=project, + zone=zone, instanceGroupManager=instance_group_manager, + body=instances_to_delete) + response = requestDelInstance.execute() + if debug > 0: + print('Request to delete instance ' + instance) + pprint(response) + + return response + +def getInstanceTemplateInfo(): + requestTemplateName = \ + service.instanceGroupManagers().get(project=project, zone=zone, + instanceGroupManager=instance_group_manager, + fields='instanceTemplate') + responseTemplateName = requestTemplateName.execute() + template_name = '' + + if debug > 1: + print('Request for the template name') + pprint(responseTemplateName) + + if len(responseTemplateName) > 0: + template_url = responseTemplateName.get('instanceTemplate') + template_url_partitioned = template_url.split('/') + template_name = \ + template_url_partitioned[len(template_url_partitioned) - 1] + + requestInstanceTemplate = \ + service.instanceTemplates().get(project=project, + instanceTemplate=template_name, fields='properties') + responseInstanceTemplateInfo = requestInstanceTemplate.execute() + + if debug > 1: + print('Template information') + pprint(responseInstanceTemplateInfo['properties']) + + machine_type = responseInstanceTemplateInfo['properties']['machineType'] + is_preemtible = responseInstanceTemplateInfo['properties']['scheduling']['preemptible'] + if debug > 0: + print('Machine Type: ' + machine_type) + print('Is preemtible: ' + str(is_preemtible)) + request = service.machineTypes().get(project=project, zone=zone, + machineType=machine_type) + response = request.execute() + guest_cpus = response['guestCpus'] + if debug > 1: + print('Machine information') + pprint(responseInstanceTemplateInfo['properties']) + if debug > 0: + print('Guest CPUs: ' + str(guest_cpus)) + + instanceTemlateInfo = {'machine_type': machine_type, + 'is_preemtible': is_preemtible, + 'guest_cpus': guest_cpus} + return instanceTemlateInfo + + +# Obtain credentials +credentials = GoogleCredentials.get_application_default() +service = discovery.build('compute', 'v1', credentials=credentials) + +# Get total number of jobs in the queue that includes number of jos waiting as well as number of jobs already assigned to nodes +queue_length_req = 'condor_q -totals -format "%d " Jobs -format "%d " Idle -format "%d " Held' +queue_length_resp = os.popen(queue_length_req).read().split() + +if len(queue_length_resp) > 1: + queue = int(queue_length_resp[0]) + idle_jobs = int(queue_length_resp[1]) + on_hold_jobs = int(queue_length_resp[2]) +else: + queue = 0 + idle_jobs = 0 + on_hold_jobs = 0 + +print('Total queue length: ' + str(queue)) +print('Idle jobs: ' + str(idle_jobs)) +print('Jobs on hold: ' + str(on_hold_jobs)) + +instanceTemlateInfo = getInstanceTemplateInfo() +if debug > 1: + print('Information about the compute instance template') + pprint(instanceTemlateInfo) + +cores_per_node = instanceTemlateInfo['guest_cpus'] +print('Number of CPU per compute node: ' + str(cores_per_node)) + +# Get state for for all jobs in Condor +name_req = 'condor_status -af name state' +slot_names = os.popen(name_req).read().splitlines() +if debug > 1: + print('Currently running jobs in Condor') + print(slot_names) + +# Adjust current queue length by the number of jos that are on-hold +queue -=on_hold_jobs +if on_hold_jobs>0: + print("Adjusted queue length: " + str(queue)) + +# Calculate number instances to satisfy current job queue length +if queue > 0: + size = int(math.ceil(float(queue) / float(cores_per_node))) + if debug>0: + print("Calucalting size of MIG: ⌈" + str(queue) + "/" + str(cores_per_node) + "⌉ = " + str(size)) +else: + size = 0 + +# If compute instance limit is specified, can not start more instances then specified in the limit +if compute_instance_limit > 0 and size > compute_instance_limit: + size = compute_instance_limit; + print("MIG target size will be limited by " + str(compute_instance_limit)) + +print('New MIG target size: ' + str(size)) + +# Get current number of instances in the MIG +requestGroupInfo = service.instanceGroupManagers().get(project=project, + zone=zone, instanceGroupManager=instance_group_manager) +responseGroupInfo = requestGroupInfo.execute() +currentTarget = int(responseGroupInfo['targetSize']) +print('Current MIG target size: ' + str(currentTarget)) + +if debug > 1: + print('MIG Information:') + print(responseGroupInfo) + +if size == 0 and currentTarget == 0: + print('No jobs in the queue and no compute instances running. Nothing to do') + exit() + +if size == currentTarget: + print('Running correct number of compute nodes to handle number of jobs in the queue') + exit() + + +if size < currentTarget: + print('Scaling down. Looking for nodes that can be shut down' ) + # Find nodes that are not busy (all slots showing status as "Unclaimed") + + node_busy = {} + for slot_name in slot_names: + name_status = slot_name.split() + if len(name_status) > 1: + name = name_status[0] + status = name_status[1] + slot = "NO-SLOT" + slot_server = name.split('@') + if len(slot_server) > 1: + slot = slot_server[0] + server = slot_server[1].split('.')[0] + else: + server = slot_server[0].split('.')[0] + + if debug > 0: + print(slot + ', ' + server + ', ' + status + '\n') + + if server not in node_busy: + if status == 'Unclaimed': + node_busy[server] = False + else: + node_busy[server] = True + else: + if status != 'Unclaimed': + node_busy[server] = True + + if debug > 1: + print('Compuute node busy status:') + print(node_busy) + + # Shut down nodes that are not busy + for node in node_busy: + if not node_busy[node]: + print('Will shut down: ' + node + ' ...') + respDel = deleteFromMig(node) + if debug > 1: + print("Shut down request for compute node " + node) + pprint(respDel) + + if debug > 1: + print("Scaling down complete") + +if size > currentTarget: + print("Scaling up. Need to increase number of instances to " + str(size)) + #Request to resize + request = service.instanceGroupManagers().resize(project=project, + zone=zone, + instanceGroupManager=instance_group_manager, + size=size) + response = request.execute() + if debug > 1: + print('Requesting to increase MIG size') + pprint(response) + print("Scaling up complete") diff --git a/docs/tutorials/multinode/terraform/htcondor/noise.py b/docs/tutorials/multinode/terraform/htcondor/noise.py new file mode 100644 index 00000000..df87cc55 --- /dev/null +++ b/docs/tutorials/multinode/terraform/htcondor/noise.py @@ -0,0 +1,24 @@ +#!/usr/bin/python3 + +import cirq, qsimcirq + +# Create a Bell state, |00) + |11) +q0, q1 = cirq.LineQubit.range(2) +circuit = cirq.Circuit( + cirq.H(q0), + cirq.CNOT(q0, q1), + cirq.measure(q0, q1, key='m') +) + +# Constructs a noise model that adds depolarizing noise after each gate. +noise = cirq.NoiseModel.from_noise_model_like(cirq.depolarize(p=0.05)) + +# Use the noise model to create a noisy circuit. +noisy_circuit = cirq.Circuit(noise.noisy_moments(circuit, system_qubits=[q0, q1])) + +sim = qsimcirq.QSimSimulator() +result = sim.run(noisy_circuit, repetitions=1000) +# Outputs a histogram dict of result:count pairs. +# Expected result is a bunch of 0s and 3s, with fewer 1s and 2s. +# (For comparison, the noiseless circuit will only have 0s and 3s) +print(result.histogram(key='m')) \ No newline at end of file diff --git a/docs/tutorials/multinode/terraform/htcondor/noise.sub b/docs/tutorials/multinode/terraform/htcondor/noise.sub new file mode 100644 index 00000000..aa5fff28 --- /dev/null +++ b/docs/tutorials/multinode/terraform/htcondor/noise.sub @@ -0,0 +1,12 @@ +universe = docker +docker_image = gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim +executable = /usr/bin/python3 +arguments = noise.py +transfer_input_files = noise.py +should_transfer_files = YES +when_to_transfer_output = ON_EXIT +output = out/out.$(Cluster)-$(Process) +error = out/err.$(Cluster)-$(Process) +log = out/log.$(Cluster)-$(Process) +request_memory = 1GB +queue 100 diff --git a/docs/tutorials/multinode/terraform/htcondor/resources.tf b/docs/tutorials/multinode/terraform/htcondor/resources.tf index 30c68824..ab00a471 100644 --- a/docs/tutorials/multinode/terraform/htcondor/resources.tf +++ b/docs/tutorials/multinode/terraform/htcondor/resources.tf @@ -50,11 +50,11 @@ variable "metric_target_queue" { } variable "compute_instance_type" { type = string - default = "n2-standard-16" + default = "n1-standard-1" } variable "instance_type" { type = string - default = "n2-standard-4" + default = "n1-standard-1" } variable "service_account" { type = string @@ -264,7 +264,7 @@ resource "google_compute_instance_group_manager" "condor-compute-igm" { target_size = "1" update_policy { - max_surge_fixed = 5 + max_surge_fixed = 2 minimal_action = "REPLACE" type = "OPPORTUNISTIC" } @@ -290,7 +290,7 @@ resource "google_compute_autoscaler" "condor-compute-as" { zone = var.zone autoscaling_policy { - cooldown_period = "60" + cooldown_period = 30 max_replicas = var.max_replicas min_replicas = var.min_replicas From c079642741fe920fcbf6256cd6ffa041338b6940 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 6 Aug 2021 11:25:27 -0700 Subject: [PATCH 024/246] Prune GPU pybindings --- pybind_interface/pybind_main.h | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/pybind_interface/pybind_main.h b/pybind_interface/pybind_main.h index c1ce850e..90bcab6e 100644 --- a/pybind_interface/pybind_main.h +++ b/pybind_interface/pybind_main.h @@ -362,28 +362,5 @@ std::vector> qsimh_simulate(const py::dict &options); "Call the qtrajectory simulator for expectation value simulation"); \ \ /* Method for hybrid simulation */ \ - m.def("qsimh_simulate", &qsimh_simulate, "Call the qsimh simulator"); \ - \ - m.def("add_gate", &add_gate, "Adds a gate to the given circuit."); \ - m.def("add_diagonal_gate", &add_diagonal_gate, \ - "Adds a two- or three-qubit diagonal gate to the given circuit."); \ - m.def("add_matrix_gate", &add_matrix_gate, \ - "Adds a matrix-defined gate to the given circuit."); \ - m.def("control_last_gate", &control_last_gate, \ - "Applies controls to the final gate of a circuit."); \ - \ - m.def("add_gate_channel", &add_gate_channel, \ - "Adds a gate to the given noisy circuit."); \ - m.def("add_diagonal_gate_channel", &add_diagonal_gate_channel, \ - "Adds a two- or three-qubit diagonal gate to the given noisy circuit."); \ - m.def("add_matrix_gate_channel", &add_matrix_gate_channel, \ - "Adds a matrix-defined gate to the given noisy circuit."); \ - m.def("control_last_gate_channel", &control_last_gate_channel, \ - "Applies controls to the final channel of a noisy circuit."); \ - \ - m.def("add_channel", &add_channel, \ - "Adds a channel to the given noisy circuit."); \ - \ - m.def("add_gate_to_opstring", &add_gate_to_opstring, \ - "Adds a gate to the given opstring."); + m.def("qsimh_simulate", &qsimh_simulate, "Call the qsimh simulator"); #endif From ed1619a9b36775d99d90c89f1d12b9581c5d5917 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 6 Aug 2021 11:26:03 -0700 Subject: [PATCH 025/246] Experimental GPU support. --- qsimcirq/qsim_simulator.py | 23 +++++++++++++---------- qsimcirq_tests/qsimcirq_test.py | 28 +++++++++++++--------------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index d3516f41..cbdabc87 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -33,7 +33,7 @@ import numpy as np -from . import qsim +from . import qsim, qsim_gpu import qsimcirq.qsim_circuit as qsimc @@ -93,6 +93,7 @@ def __init__( applied to all circuits run using this simulator. Accepted keys and their behavior are as follows: - 'f': int (> 0). Maximum size of fused gates. Default: 2. + - 'g': bool. If true, simulate with GPU. Default: false. - 'r': int (> 0). Noisy repetitions (see below). Default: 1. - 't': int (> 0). Number of threads to run on. Default: 1. - 'v': int (>= 0). Log verbosity. Default: 0. @@ -119,6 +120,8 @@ def __init__( "used in QSimCircuit instantiation." ) self._prng = value.parse_random_state(seed) + # module to use for simulation + self._sim_module = qsim_gpu if qsim_options['g'] else qsim self.qsim_options = {"t": 1, "f": 2, "v": 0, "r": 1} self.qsim_options.update(qsim_options) # Deque of (, ) tuples. @@ -220,10 +223,10 @@ def _sample_measure_results( noisy = _needs_trajectories(program) if noisy: translator_fn_name = "translate_cirq_to_qtrajectory" - sampler_fn = qsim.qtrajectory_sample + sampler_fn = self._sim_module.qtrajectory_sample else: translator_fn_name = "translate_cirq_to_qsim" - sampler_fn = qsim.qsim_sample + sampler_fn = self._sim_module.qsim_sample if not noisy and program.are_all_measurements_terminal() and repetitions > 1: print( @@ -246,7 +249,7 @@ def _sample_measure_results( ops.QubitOrder.DEFAULT, ) options["s"] = self.get_seed() - final_state = qsim.qsim_simulate_fullstate(options, 0) + final_state = self._sim_module.qsim_simulate_fullstate(options, 0) full_results = sim.sample_state_vector( final_state.view(np.complex64), range(num_qubits), @@ -319,10 +322,10 @@ def compute_amplitudes_sweep( trials_results = [] if _needs_trajectories(program): translator_fn_name = "translate_cirq_to_qtrajectory" - simulator_fn = qsim.qtrajectory_simulate + simulator_fn = self._sim_module.qtrajectory_simulate else: translator_fn_name = "translate_cirq_to_qsim" - simulator_fn = qsim.qsim_simulate + simulator_fn = self._sim_module.qsim_simulate for prs in param_resolvers: solved_circuit = protocols.resolve_parameters(program, prs) @@ -398,10 +401,10 @@ def simulate_sweep( trials_results = [] if _needs_trajectories(program): translator_fn_name = "translate_cirq_to_qtrajectory" - fullstate_simulator_fn = qsim.qtrajectory_simulate_fullstate + fullstate_simulator_fn = self._sim_module.qtrajectory_simulate_fullstate else: translator_fn_name = "translate_cirq_to_qsim" - fullstate_simulator_fn = qsim.qsim_simulate_fullstate + fullstate_simulator_fn = self._sim_module.qsim_simulate_fullstate for prs in param_resolvers: solved_circuit = protocols.resolve_parameters(program, prs) @@ -525,10 +528,10 @@ def simulate_expectation_values_sweep( results = [] if _needs_trajectories(program): translator_fn_name = "translate_cirq_to_qtrajectory" - ev_simulator_fn = qsim.qtrajectory_simulate_expectation_values + ev_simulator_fn = self._sim_module.qtrajectory_simulate_expectation_values else: translator_fn_name = "translate_cirq_to_qsim" - ev_simulator_fn = qsim.qsim_simulate_expectation_values + ev_simulator_fn = self._sim_module.qsim_simulate_expectation_values for prs in param_resolvers: solved_circuit = protocols.resolve_parameters(program, prs) diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index ba72935f..914c7820 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -1063,21 +1063,19 @@ def test_cirq_qsimh_simulate(): def test_cirq_qsim_gpu_simulate(): - # Dummy test. - print(qsimcirq.qsim_gpu) - assert False - # # Pick qubits. - # a, b = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] - - # # Create a circuit - # cirq_circuit = cirq.Circuit(cirq.CNOT(a, b), cirq.CNOT(b, a), cirq.X(a)) - - # qsimh_options = {"k": [0], "w": 0, "p": 1, "r": 1} - # qsimhSim = qsimcirq.QSimhSimulator(qsimh_options) - # result = qsimhSim.compute_amplitudes( - # cirq_circuit, bitstrings=[0b00, 0b01, 0b10, 0b11] - # ) - # assert np.allclose(result, [0j, 0j, (1 + 0j), 0j]) + # Pick qubits. + a, b = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] + + # Create a circuit + cirq_circuit = cirq.Circuit(cirq.CNOT(a, b), cirq.CNOT(b, a), cirq.X(a)) + + # Enable GPU acceleration. + gpu_options = {'g': True} + qsimGpuSim = qsimcirq.QSimSimulator(qsim_options=gpu_options) + result = qsimGpuSim.compute_amplitudes( + cirq_circuit, bitstrings=[0b00, 0b01, 0b10, 0b11] + ) + assert np.allclose(result, [0j, 0j, (1 + 0j), 0j]) def test_cirq_qsim_params(): From 5b0de1d46d7558b6f9936c018a25304dbf15cec7 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Sat, 7 Aug 2021 19:28:03 +0000 Subject: [PATCH 026/246] Created Autoscaler Object. --- .../multinode/terraform/autoscaler.py | 509 +++++++++--------- 1 file changed, 263 insertions(+), 246 deletions(-) diff --git a/docs/tutorials/multinode/terraform/autoscaler.py b/docs/tutorials/multinode/terraform/autoscaler.py index af68cf45..f03ba406 100644 --- a/docs/tutorials/multinode/terraform/autoscaler.py +++ b/docs/tutorials/multinode/terraform/autoscaler.py @@ -18,6 +18,8 @@ # Script for resizing managed instance group (MIG) cluster size based # on the number of jobs in the Condor Queue. +from absl import app +from absl import flags from pprint import pprint from googleapiclient import discovery from oauth2client.client import GoogleCredentials @@ -26,250 +28,265 @@ import math import argparse -parser = argparse.ArgumentParser("autoscaler.py") -parser.add_argument("-p", "--project_id", help="Project id", type=str) -parser.add_argument("-r", "--region", help="GCP region where the managed instance group is located", type=str) -parser.add_argument("-z", "--zone", help="Name of GCP zone where the managed instance group is located", type=str) -parser.add_argument("-g", "--group_manager", help="Name of the managed instance group", type=str) -parser.add_argument("-c", "--computeinstancelimit", help="Maximum number of compute instances", type=int) -parser.add_argument("-v", "--verbosity", help="Increase output verbosity. 1-show basic debug info. 2-show detail debug info", type=int, choices=[0, 1, 2]) -args = parser.parse_args() -# Project ID -project = args.project_id # Ex:'slurm-var-demo' - -# Region where the managed instance group is located -region = args.region # Ex: 'us-central1' - -# Name of the zone where the managed instance group is located -zone = args.zone # Ex: 'us-central1-f' - -# The name of the managed instance group. -instance_group_manager = args.group_manager # Ex: 'condor-compute-igm' - -# Default number of cores per intance, will be replaced with actual value -cores_per_node = 4 - -# Default number of running instances that the managed instance group should maintain at any given time. This number will go up and down based on the load (number of jobs in the queue) -size = 0 - -# Debug level: 1-print debug information, 2 - print detail debug information -debug = 0 -if (args.verbosity): - debug = args.verbosity - -# Limit for the maximum number of compute instance. If zero (default setting), no limit will be enforced by the script -compute_instance_limit = 0 -if (args.computeinstancelimit): - compute_instance_limit = abs(args.computeinstancelimit) - - -if debug > 1: - print('Launching autoscaler.py with the following arguments:') - print('project_id: ' + project) - print('region: ' + region) - print('zone: ' + zone) - print('group_manager: ' + instance_group_manager) - print('computeinstancelimit: ' + str(compute_instance_limit)) - print('debuglevel: ' + str(debug)) - - -# Remove specified instance from MIG and decrease MIG size -def deleteFromMig(instance): - instanceUrl = 'https://www.googleapis.com/compute/v1/projects/' \ - + project + '/zones/' + zone + '/instances/' + instance - instances_to_delete = {'instances': [instanceUrl]} - - requestDelInstance = \ - service.instanceGroupManagers().deleteInstances(project=project, - zone=zone, instanceGroupManager=instance_group_manager, - body=instances_to_delete) - response = requestDelInstance.execute() - if debug > 0: - print('Request to delete instance ' + instance) - pprint(response) - - return response - -def getInstanceTemplateInfo(): - requestTemplateName = \ - service.instanceGroupManagers().get(project=project, zone=zone, - instanceGroupManager=instance_group_manager, - fields='instanceTemplate') - responseTemplateName = requestTemplateName.execute() - template_name = '' - - if debug > 1: - print('Request for the template name') - pprint(responseTemplateName) - - if len(responseTemplateName) > 0: - template_url = responseTemplateName.get('instanceTemplate') - template_url_partitioned = template_url.split('/') - template_name = \ - template_url_partitioned[len(template_url_partitioned) - 1] - - requestInstanceTemplate = \ - service.instanceTemplates().get(project=project, - instanceTemplate=template_name, fields='properties') - responseInstanceTemplateInfo = requestInstanceTemplate.execute() - - if debug > 1: - print('Template information') - pprint(responseInstanceTemplateInfo['properties']) - - machine_type = responseInstanceTemplateInfo['properties']['machineType'] - is_preemtible = responseInstanceTemplateInfo['properties']['scheduling']['preemptible'] - if debug > 0: - print('Machine Type: ' + machine_type) - print('Is preemtible: ' + str(is_preemtible)) - request = service.machineTypes().get(project=project, zone=zone, - machineType=machine_type) - response = request.execute() - guest_cpus = response['guestCpus'] - if debug > 1: - print('Machine information') - pprint(responseInstanceTemplateInfo['properties']) - if debug > 0: - print('Guest CPUs: ' + str(guest_cpus)) - - instanceTemlateInfo = {'machine_type': machine_type, - 'is_preemtible': is_preemtible, - 'guest_cpus': guest_cpus} - return instanceTemlateInfo - - -# Obtain credentials -credentials = GoogleCredentials.get_application_default() -service = discovery.build('compute', 'v1', credentials=credentials) - -# Get total number of jobs in the queue that includes number of jos waiting as well as number of jobs already assigned to nodes -queue_length_req = 'condor_q -totals -format "%d " Jobs -format "%d " Idle -format "%d " Held' -queue_length_resp = os.popen(queue_length_req).read().split() - -if len(queue_length_resp) > 1: - queue = int(queue_length_resp[0]) - idle_jobs = int(queue_length_resp[1]) - on_hold_jobs = int(queue_length_resp[2]) -else: - queue = 0 - idle_jobs = 0 - on_hold_jobs = 0 - -print('Total queue length: ' + str(queue)) -print('Idle jobs: ' + str(idle_jobs)) -print('Jobs on hold: ' + str(on_hold_jobs)) - -instanceTemlateInfo = getInstanceTemplateInfo() -if debug > 1: - print('Information about the compute instance template') - pprint(instanceTemlateInfo) - -cores_per_node = instanceTemlateInfo['guest_cpus'] -print('Number of CPU per compute node: ' + str(cores_per_node)) - -# Get state for for all jobs in Condor -name_req = 'condor_status -af name state' -slot_names = os.popen(name_req).read().splitlines() -if debug > 1: - print('Currently running jobs in Condor') - print(slot_names) - -# Adjust current queue length by the number of jos that are on-hold -queue -=on_hold_jobs -if on_hold_jobs>0: - print("Adjusted queue length: " + str(queue)) - -# Calculate number instances to satisfy current job queue length -if queue > 0: - size = int(math.ceil(float(queue) / float(cores_per_node))) - if debug>0: - print("Calucalting size of MIG: ⌈" + str(queue) + "/" + str(cores_per_node) + "⌉ = " + str(size)) -else: - size = 0 - -# If compute instance limit is specified, can not start more instances then specified in the limit -if compute_instance_limit > 0 and size > compute_instance_limit: - size = compute_instance_limit; - print("MIG target size will be limited by " + str(compute_instance_limit)) - -print('New MIG target size: ' + str(size)) - -# Get current number of instances in the MIG -requestGroupInfo = service.instanceGroupManagers().get(project=project, - zone=zone, instanceGroupManager=instance_group_manager) -responseGroupInfo = requestGroupInfo.execute() -currentTarget = int(responseGroupInfo['targetSize']) -print('Current MIG target size: ' + str(currentTarget)) - -if debug > 1: - print('MIG Information:') - print(responseGroupInfo) - -if size == 0 and currentTarget == 0: - print('No jobs in the queue and no compute instances running. Nothing to do') - exit() - -if size == currentTarget: - print('Running correct number of compute nodes to handle number of jobs in the queue') - exit() - - -if size < currentTarget: - print('Scaling down. Looking for nodes that can be shut down' ) - # Find nodes that are not busy (all slots showing status as "Unclaimed") - - node_busy = {} - for slot_name in slot_names: - name_status = slot_name.split() - if len(name_status) > 1: - name = name_status[0] - status = name_status[1] - slot = "NO-SLOT" - slot_server = name.split('@') - if len(slot_server) > 1: - slot = slot_server[0] - server = slot_server[1].split('.')[0] - else: - server = slot_server[0].split('.')[0] - - if debug > 0: - print(slot + ', ' + server + ', ' + status + '\n') - - if server not in node_busy: - if status == 'Unclaimed': - node_busy[server] = False - else: - node_busy[server] = True - else: - if status != 'Unclaimed': - node_busy[server] = True - - if debug > 1: - print('Compuute node busy status:') - print(node_busy) - - # Shut down nodes that are not busy - for node in node_busy: - if not node_busy[node]: - print('Will shut down: ' + node + ' ...') - respDel = deleteFromMig(node) - if debug > 1: - print("Shut down request for compute node " + node) - pprint(respDel) - - if debug > 1: - print("Scaling down complete") - -if size > currentTarget: - print("Scaling up. Need to increase number of instances to " + str(size)) - #Request to resize - request = service.instanceGroupManagers().resize(project=project, - zone=zone, - instanceGroupManager=instance_group_manager, - size=size) - response = request.execute() - if debug > 1: - print('Requesting to increase MIG size') - pprint(response) - print("Scaling up complete") +class AutoScaler(): + + def __init__(self): + parser = argparse.ArgumentParser("autoscaler.py") + parser.add_argument("-p", "--project_id", help="Project id", type=str) + parser.add_argument("-r", "--region", help="GCP region where the managed instance group is located", type=str) + parser.add_argument("-z", "--zone", help="Name of GCP zone where the managed instance group is located", type=str) + parser.add_argument("-g", "--group_manager", help="Name of the managed instance group", type=str) + parser.add_argument("-c", "--computeinstancelimit", help="Maximum number of compute instances", type=int) + parser.add_argument("-v", "--verbosity", help="Increase output verbosity. 1-show basic debug info. 2-show detail debug info", type=int, choices=[0, 1, 2]) + + args = parser.parse_args() + + # Project ID + self.project = args.project_id # Ex:'slurm-var-demo' + + # Region where the managed instance group is located + self.region = args.region # Ex: 'us-central1' + + # Name of the zone where the managed instance group is located + self.zone = args.zone # Ex: 'us-central1-f' + + # The name of the managed instance group. + self.instance_group_manager = args.group_manager # Ex: 'condor-compute-igm' + + # Default number of cores per intance, will be replaced with actual value + self.cores_per_node = 4 + + # Default number of running instances that the managed instance group should maintain at any given time. This number will go up and down based on the load (number of jobs in the queue) + self.size = 0 + + # Debug level: 1-print debug information, 2 - print detail debug information + self.debug = 0 + if (args.verbosity): + self.debug = args.verbosity + + # Limit for the maximum number of compute instance. If zero (default setting), no limit will be enforced by the script + self.compute_instance_limit = 0 + if (args.computeinstancelimit): + self.compute_instance_limit = abs(args.computeinstancelimit) + + + if self.debug > 1: + print('Launching autoscaler.py with the following arguments:') + print('project_id: ' + self.project) + print('region: ' + self.region) + print('zone: ' + self.zone) + print('group_manager: ' + self.instance_group_manager) + print('computeinstancelimit: ' + str(self.compute_instance_limit)) + print('debuglevel: ' + str(self.debug)) + + # Obtain credentials + self.credentials = GoogleCredentials.get_application_default() + self.service = discovery.build('compute', 'v1', credentials=self.credentials) + + + # Remove specified instance from MIG and decrease MIG size + def deleteFromMig(self, instance): + instanceUrl = 'https://www.googleapis.com/compute/v1/projects/' \ + + self.project + '/zones/' + self.zone + '/instances/' + instance + instances_to_delete = {'instances': [instanceUrl]} + + requestDelInstance = \ + self.service.instanceGroupManagers().deleteInstances(project=self.project, + zone=self.zone, instanceGroupManager=self.instance_group_manager, + body=instances_to_delete) + response = requestDelInstance.execute() + if self.debug > 0: + print('Request to delete instance ' + instance) + pprint(response) + + return response + + def getInstanceTemplateInfo(self): + requestTemplateName = \ + self.service.instanceGroupManagers().get(project=self.project, zone=self.zone, + instanceGroupManager=self.instance_group_manager, + fields='instanceTemplate') + responseTemplateName = requestTemplateName.execute() + template_name = '' + + if self.debug > 1: + print('Request for the template name') + pprint(responseTemplateName) + + if len(responseTemplateName) > 0: + template_url = responseTemplateName.get('instanceTemplate') + template_url_partitioned = template_url.split('/') + template_name = \ + template_url_partitioned[len(template_url_partitioned) - 1] + + requestInstanceTemplate = \ + self.service.instanceTemplates().get(project=self.project, + instanceTemplate=template_name, fields='properties') + responseInstanceTemplateInfo = requestInstanceTemplate.execute() + + if self.debug > 1: + print('Template information') + pprint(responseInstanceTemplateInfo['properties']) + + machine_type = responseInstanceTemplateInfo['properties']['machineType'] + is_preemtible = responseInstanceTemplateInfo['properties']['scheduling']['preemptible'] + if self.debug > 0: + print('Machine Type: ' + machine_type) + print('Is preemtible: ' + str(is_preemtible)) + request = self.service.machineTypes().get(project=self.project, zone=self.zone, + machineType=machine_type) + response = request.execute() + guest_cpus = response['guestCpus'] + if self.debug > 1: + print('Machine information') + pprint(responseInstanceTemplateInfo['properties']) + if self.debug > 0: + print('Guest CPUs: ' + str(guest_cpus)) + + instanceTemlateInfo = {'machine_type': machine_type, + 'is_preemtible': is_preemtible, + 'guest_cpus': guest_cpus} + return instanceTemlateInfo + + + + def scale(self): + # Get total number of jobs in the queue that includes number of jos waiting as well as number of jobs already assigned to nodes + queue_length_req = 'condor_q -totals -format "%d " Jobs -format "%d " Idle -format "%d " Held' + queue_length_resp = os.popen(queue_length_req).read().split() + + if len(queue_length_resp) > 1: + queue = int(queue_length_resp[0]) + idle_jobs = int(queue_length_resp[1]) + on_hold_jobs = int(queue_length_resp[2]) + else: + queue = 0 + idle_jobs = 0 + on_hold_jobs = 0 + + print('Total queue length: ' + str(queue)) + print('Idle jobs: ' + str(idle_jobs)) + print('Jobs on hold: ' + str(on_hold_jobs)) + + instanceTemlateInfo = self.getInstanceTemplateInfo() + if self.debug > 1: + print('Information about the compute instance template') + pprint(instanceTemlateInfo) + + self.cores_per_node = instanceTemlateInfo['guest_cpus'] + print('Number of CPU per compute node: ' + str(self.cores_per_node)) + + # Get state for for all jobs in Condor + name_req = 'condor_status -af name state' + slot_names = os.popen(name_req).read().splitlines() + if self.debug > 1: + print('Currently running jobs in Condor') + print(slot_names) + + # Adjust current queue length by the number of jos that are on-hold + queue -=on_hold_jobs + if on_hold_jobs>0: + print("Adjusted queue length: " + str(queue)) + + # Calculate number instances to satisfy current job queue length + if queue > 0: + self.size = int(math.ceil(float(queue) / float(self.cores_per_node))) + if self.debug>0: + print("Calucalting size of MIG: ⌈" + str(queue) + "/" + str(self.cores_per_node) + "⌉ = " + str(self.size)) + else: + self.size = 0 + + # If compute instance limit is specified, can not start more instances then specified in the limit + if self.compute_instance_limit > 0 and self.size > self.compute_instance_limit: + self.size = self.compute_instance_limit + print("MIG target size will be limited by " + str(self.compute_instance_limit)) + + print('New MIG target size: ' + str(self.size)) + + # Get current number of instances in the MIG + requestGroupInfo = self.service.instanceGroupManagers().get(project=self.project, + zone=self.zone, instanceGroupManager=self.instance_group_manager) + responseGroupInfo = requestGroupInfo.execute() + currentTarget = int(responseGroupInfo['targetSize']) + print('Current MIG target size: ' + str(currentTarget)) + + if self.debug > 1: + print('MIG Information:') + print(responseGroupInfo) + + if self.size == 0 and currentTarget == 0: + print('No jobs in the queue and no compute instances running. Nothing to do') + exit() + + if self.size == currentTarget: + print('Running correct number of compute nodes to handle number of jobs in the queue') + exit() + + + if self.size < currentTarget: + print('Scaling down. Looking for nodes that can be shut down' ) + # Find nodes that are not busy (all slots showing status as "Unclaimed") + + node_busy = {} + for slot_name in slot_names: + name_status = slot_name.split() + if len(name_status) > 1: + name = name_status[0] + status = name_status[1] + slot = "NO-SLOT" + slot_server = name.split('@') + if len(slot_server) > 1: + slot = slot_server[0] + server = slot_server[1].split('.')[0] + else: + server = slot_server[0].split('.')[0] + + if self.debug > 0: + print(slot + ', ' + server + ', ' + status + '\n') + + if server not in node_busy: + if status == 'Unclaimed': + node_busy[server] = False + else: + node_busy[server] = True + else: + if status != 'Unclaimed': + node_busy[server] = True + + if self.debug > 1: + print('Compuute node busy status:') + print(node_busy) + + # Shut down nodes that are not busy + for node in node_busy: + if not node_busy[node]: + print('Will shut down: ' + node + ' ...') + respDel = self.deleteFromMig(node) + if self.debug > 1: + print("Shut down request for compute node " + node) + pprint(respDel) + + if self.debug > 1: + print("Scaling down complete") + + if self.size > currentTarget: + print("Scaling up. Need to increase number of instances to " + str(self.size)) + #Request to resize + request = self.service.instanceGroupManagers().resize(project=self.project, + zone=self.zone, + instanceGroupManager=self.instance_group_manager, + size=self.size) + response = request.execute() + if self.debug > 1: + print('Requesting to increase MIG size') + pprint(response) + print("Scaling up complete") +def main(): + + scaler = AutoScaler() + scaler.scale() + + +if __name__ == "__main__": + app.run(main) \ No newline at end of file From 9c507754f7605f91d689f9898f6aa97e446db443 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Sat, 7 Aug 2021 19:48:05 +0000 Subject: [PATCH 027/246] FIxed arg parsing and recorded pip installs --- .../multinode/terraform/autoscaler.py | 80 ++++++++++--------- .../terraform/htcondor/startup-centos.sh | 7 ++ 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/docs/tutorials/multinode/terraform/autoscaler.py b/docs/tutorials/multinode/terraform/autoscaler.py index f03ba406..a3cf1b12 100644 --- a/docs/tutorials/multinode/terraform/autoscaler.py +++ b/docs/tutorials/multinode/terraform/autoscaler.py @@ -28,49 +28,21 @@ import math import argparse +parser = argparse.ArgumentParser("autoscaler.py") +parser.add_argument("-p", "--project_id", help="Project id", type=str) +parser.add_argument("-r", "--region", help="GCP region where the managed instance group is located", type=str) +parser.add_argument("-z", "--zone", help="Name of GCP zone where the managed instance group is located", type=str) +parser.add_argument("-g", "--group_manager", help="Name of the managed instance group", type=str) +parser.add_argument("-c", "--computeinstancelimit", help="Maximum number of compute instances", type=int) +parser.add_argument("-v", "--verbosity", help="Increase output verbosity. 1-show basic debug info. 2-show detail debug info", type=int, choices=[0, 1, 2]) + +args = parser.parse_args() + class AutoScaler(): def __init__(self): - parser = argparse.ArgumentParser("autoscaler.py") - parser.add_argument("-p", "--project_id", help="Project id", type=str) - parser.add_argument("-r", "--region", help="GCP region where the managed instance group is located", type=str) - parser.add_argument("-z", "--zone", help="Name of GCP zone where the managed instance group is located", type=str) - parser.add_argument("-g", "--group_manager", help="Name of the managed instance group", type=str) - parser.add_argument("-c", "--computeinstancelimit", help="Maximum number of compute instances", type=int) - parser.add_argument("-v", "--verbosity", help="Increase output verbosity. 1-show basic debug info. 2-show detail debug info", type=int, choices=[0, 1, 2]) - - args = parser.parse_args() - - # Project ID - self.project = args.project_id # Ex:'slurm-var-demo' - - # Region where the managed instance group is located - self.region = args.region # Ex: 'us-central1' - - # Name of the zone where the managed instance group is located - self.zone = args.zone # Ex: 'us-central1-f' - - # The name of the managed instance group. - self.instance_group_manager = args.group_manager # Ex: 'condor-compute-igm' - - # Default number of cores per intance, will be replaced with actual value - self.cores_per_node = 4 - - # Default number of running instances that the managed instance group should maintain at any given time. This number will go up and down based on the load (number of jobs in the queue) - self.size = 0 - - # Debug level: 1-print debug information, 2 - print detail debug information - self.debug = 0 - if (args.verbosity): - self.debug = args.verbosity - - # Limit for the maximum number of compute instance. If zero (default setting), no limit will be enforced by the script - self.compute_instance_limit = 0 - if (args.computeinstancelimit): - self.compute_instance_limit = abs(args.computeinstancelimit) - - + if self.debug > 1: print('Launching autoscaler.py with the following arguments:') print('project_id: ' + self.project) @@ -285,6 +257,36 @@ def scale(self): def main(): scaler = AutoScaler() + + # Project ID + scaler.project = args.project_id # Ex:'slurm-var-demo' + + # Region where the managed instance group is located + scaler.region = args.region # Ex: 'us-central1' + + # Name of the zone where the managed instance group is located + scaler.zone = args.zone # Ex: 'us-central1-f' + + # The name of the managed instance group. + scaler.instance_group_manager = args.group_manager # Ex: 'condor-compute-igm' + + # Default number of cores per intance, will be replaced with actual value + scaler.cores_per_node = 4 + + # Default number of running instances that the managed instance group should maintain at any given time. This number will go up and down based on the load (number of jobs in the queue) + scaler.size = 0 + + # Debug level: 1-print debug information, 2 - print detail debug information + scaler.debug = 0 + if (args.verbosity): + scaler.debug = args.verbosity + + # Limit for the maximum number of compute instance. If zero (default setting), no limit will be enforced by the script + scaler.compute_instance_limit = 0 + if (args.computeinstancelimit): + scaler.compute_instance_limit = abs(args.computeinstancelimit) + + scaler.scale() diff --git a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh index 66220888..cc5dbedc 100644 --- a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh +++ b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh @@ -167,3 +167,10 @@ chmod 666 /var/log/condor/jobs/stats.log fi service google-fluentd restart + +# Add Python Libraries and Autoscaler +if [ "$SERVER_TYPE" == "submit" ]; then + python3 -m pip install --upgrade oauth2client + python3 -m pip install --upgrade google-api-python-client + python3 -m pip install --upgrade absl-py +fi \ No newline at end of file From 2e3d31b9da8dcad43a0ada261d1655764f72565a Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Mon, 9 Aug 2021 00:41:19 +0000 Subject: [PATCH 028/246] Fixed Arg parsing --- .../multinode/terraform/autoscaler.py | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/tutorials/multinode/terraform/autoscaler.py b/docs/tutorials/multinode/terraform/autoscaler.py index a3cf1b12..8669b7da 100644 --- a/docs/tutorials/multinode/terraform/autoscaler.py +++ b/docs/tutorials/multinode/terraform/autoscaler.py @@ -28,30 +28,20 @@ import math import argparse -parser = argparse.ArgumentParser("autoscaler.py") -parser.add_argument("-p", "--project_id", help="Project id", type=str) -parser.add_argument("-r", "--region", help="GCP region where the managed instance group is located", type=str) -parser.add_argument("-z", "--zone", help="Name of GCP zone where the managed instance group is located", type=str) -parser.add_argument("-g", "--group_manager", help="Name of the managed instance group", type=str) -parser.add_argument("-c", "--computeinstancelimit", help="Maximum number of compute instances", type=int) -parser.add_argument("-v", "--verbosity", help="Increase output verbosity. 1-show basic debug info. 2-show detail debug info", type=int, choices=[0, 1, 2]) +parser = argparse.ArgumentParser() +parser.add_argument("--p", help="Project id", type=str) +parser.add_argument("--r", help="GCP region where the managed instance group is located", type=str) +parser.add_argument("--z", help="Name of GCP zone where the managed instance group is located", type=str) +parser.add_argument("--g", help="Name of the managed instance group", type=str) +parser.add_argument("--c", help="Maximum number of compute instances", type=int) +parser.add_argument("--v", help="Increase output verbosity. 1-show basic debug info. 2-show detail debug info", type=int, choices=[0, 1, 2]) args = parser.parse_args() - class AutoScaler(): def __init__(self): - if self.debug > 1: - print('Launching autoscaler.py with the following arguments:') - print('project_id: ' + self.project) - print('region: ' + self.region) - print('zone: ' + self.zone) - print('group_manager: ' + self.instance_group_manager) - print('computeinstancelimit: ' + str(self.compute_instance_limit)) - print('debuglevel: ' + str(self.debug)) - # Obtain credentials self.credentials = GoogleCredentials.get_application_default() self.service = discovery.build('compute', 'v1', credentials=self.credentials) @@ -124,6 +114,16 @@ def getInstanceTemplateInfo(self): def scale(self): + # diagnosis + if self.debug > 1: + print('Launching autoscaler.py with the following arguments:') + print('project_id: ' + self.project) + print('region: ' + self.region) + print('zone: ' + self.zone) + print('group_manager: ' + self.instance_group_manager) + print('computeinstancelimit: ' + str(self.compute_instance_limit)) + print('debuglevel: ' + str(self.debug)) + # Get total number of jobs in the queue that includes number of jos waiting as well as number of jobs already assigned to nodes queue_length_req = 'condor_q -totals -format "%d " Jobs -format "%d " Idle -format "%d " Held' queue_length_resp = os.popen(queue_length_req).read().split() @@ -259,16 +259,16 @@ def main(): scaler = AutoScaler() # Project ID - scaler.project = args.project_id # Ex:'slurm-var-demo' + scaler.project = args.p # Ex:'slurm-var-demo' # Region where the managed instance group is located - scaler.region = args.region # Ex: 'us-central1' + scaler.region = args.r # Ex: 'us-central1' # Name of the zone where the managed instance group is located - scaler.zone = args.zone # Ex: 'us-central1-f' + scaler.zone = args.z # Ex: 'us-central1-f' # The name of the managed instance group. - scaler.instance_group_manager = args.group_manager # Ex: 'condor-compute-igm' + scaler.instance_group_manager = args.g # Ex: 'condor-compute-igm' # Default number of cores per intance, will be replaced with actual value scaler.cores_per_node = 4 @@ -278,17 +278,17 @@ def main(): # Debug level: 1-print debug information, 2 - print detail debug information scaler.debug = 0 - if (args.verbosity): - scaler.debug = args.verbosity + if (args.v): + scaler.debug = args.v # Limit for the maximum number of compute instance. If zero (default setting), no limit will be enforced by the script scaler.compute_instance_limit = 0 - if (args.computeinstancelimit): - scaler.compute_instance_limit = abs(args.computeinstancelimit) + if (args.c): + scaler.compute_instance_limit = abs(args.c) scaler.scale() if __name__ == "__main__": - app.run(main) \ No newline at end of file + main() \ No newline at end of file From 1384232526f8ce624341a654af8e36e1d295b8d7 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 9 Aug 2021 08:32:13 -0700 Subject: [PATCH 029/246] Test three modes --- qsimcirq/qsim_simulator.py | 6 ++++- qsimcirq_tests/qsimcirq_test.py | 40 ++++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index cbdabc87..66c68272 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -121,7 +121,11 @@ def __init__( ) self._prng = value.parse_random_state(seed) # module to use for simulation - self._sim_module = qsim_gpu if qsim_options['g'] else qsim + self._sim_module = ( + qsim_gpu + if 'g' in qsim_options and qsim_options['g'] + else qsim + ) self.qsim_options = {"t": 1, "f": 2, "v": 0, "r": 1} self.qsim_options.update(qsim_options) # Deque of (, ) tuples. diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 914c7820..22266f3e 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -1062,7 +1062,7 @@ def test_cirq_qsimh_simulate(): assert np.allclose(result, [0j, 0j, (1 + 0j), 0j]) -def test_cirq_qsim_gpu_simulate(): +def test_cirq_qsim_gpu_amplitudes(): # Pick qubits. a, b = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] @@ -1078,6 +1078,44 @@ def test_cirq_qsim_gpu_simulate(): assert np.allclose(result, [0j, 0j, (1 + 0j), 0j]) +def test_cirq_qsim_gpu_simulate(): + # Pick qubits. + a, b = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] + + # Create a circuit + cirq_circuit = cirq.Circuit(cirq.H(a), cirq.CNOT(a, b), cirq.X(b)) + + # Enable GPU acceleration. + gpu_options = {'g': True} + qsimGpuSim = qsimcirq.QSimSimulator(qsim_options=gpu_options) + result = qsimGpuSim.simulate(cirq_circuit) + assert result.state_vector().shape == (4,) + + cirqSim = cirq.Simulator() + cirq_result = cirqSim.simulate(cirq_circuit) + assert cirq.linalg.allclose_up_to_global_phase( + result.state_vector(), cirq_result.state_vector(), atol=1.0e-6 + ) + + +def test_cirq_qsim_gpu_expectation_values(): + # Pick qubits. + a, b = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] + + # Create a circuit + cirq_circuit = cirq.Circuit(cirq.H(a), cirq.CNOT(a, b), cirq.X(b)) + obs = [cirq.Z(a) * cirq.Z(b)] + + # Enable GPU acceleration. + gpu_options = {'g': True} + qsimGpuSim = qsimcirq.QSimSimulator(qsim_options=gpu_options) + result = qsimGpuSim.simulate_expectation_values(cirq_circuit, obs) + + cirqSim = cirq.Simulator() + cirq_result = cirqSim.simulate_expectation_values(cirq_circuit, obs) + assert np.allclose(result, cirq_result) + + def test_cirq_qsim_params(): qubit = cirq.GridQubit(0, 0) From 687ca0850657511c2dab17eb3834947967c02b27 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 9 Aug 2021 11:29:30 -0700 Subject: [PATCH 030/246] Copy GPU state for python --- lib/vectorspace.h | 4 ++++ lib/vectorspace_cuda.h | 4 ++++ pybind_interface/pybind_main.cpp | 18 +++++++++++++----- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/vectorspace.h b/lib/vectorspace.h index dc871d9f..5a5a6c94 100644 --- a/lib/vectorspace.h +++ b/lib/vectorspace.h @@ -74,6 +74,10 @@ class VectorSpace { return num_qubits_; } + bool requires_copy_to_host() const { + return false; + } + private: Pointer ptr_; unsigned num_qubits_; diff --git a/lib/vectorspace_cuda.h b/lib/vectorspace_cuda.h index c064a90b..ac228c63 100644 --- a/lib/vectorspace_cuda.h +++ b/lib/vectorspace_cuda.h @@ -67,6 +67,10 @@ class VectorSpaceCUDA { return num_qubits_; } + bool requires_copy_to_host() const { + return true; + } + private: Pointer ptr_; unsigned num_qubits_; diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 070c0f45..af36483d 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -604,11 +604,19 @@ class SimulatorHelper { py::array_t release_state_to_python() { state_space.InternalToNormalOrder(state); - uint64_t fsv_size = 2 * (uint64_t{1} << num_qubits); - float* fsv = state.release(); - auto capsule = py::capsule( - fsv, [](void *data) { detail::free(data); }); - return py::array_t(fsv_size, fsv, capsule); + if (state.requires_copy_to_host()) { + uint64_t fsv_size = state_space.MinSize(state.num_qubits()); + auto* fsv = new float[fsv_size]; + state_space.Copy(state, fsv); + auto capsule = py::capsule(fsv, [](void *data) {}); + return py::array_t(fsv_size, fsv, capsule); + } else { + uint64_t fsv_size = 2 * (uint64_t{1} << num_qubits); + float* fsv = state.release(); + auto capsule = py::capsule( + fsv, [](void *data) { detail::free(data); }); + return py::array_t(fsv_size, fsv, capsule); + } } std::vector> get_expectation_value( From 47d2e1a574dd172c7d58ec5f6c9c1e0f39201b19 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 9 Aug 2021 12:29:40 -0700 Subject: [PATCH 031/246] Min size to actual size conversion. --- pybind_interface/pybind_main.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index af36483d..ed788ee2 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -604,14 +604,21 @@ class SimulatorHelper { py::array_t release_state_to_python() { state_space.InternalToNormalOrder(state); + uint64_t fsv_size = 2 * (uint64_t{1} << num_qubits); if (state.requires_copy_to_host()) { - uint64_t fsv_size = state_space.MinSize(state.num_qubits()); + uint64_t storage_size = state_space.MinSize(state.num_qubits()); auto* fsv = new float[fsv_size]; - state_space.Copy(state, fsv); + if (storage_size != fsv_size) { + auto* temp = new float[storage_size]; + state_space.Copy(state, temp); + memcpy(fsv, temp, fsv_size * sizeof(float)); + delete [] temp; + } else { + state_space.Copy(state, fsv); + } auto capsule = py::capsule(fsv, [](void *data) {}); return py::array_t(fsv_size, fsv, capsule); } else { - uint64_t fsv_size = 2 * (uint64_t{1} << num_qubits); float* fsv = state.release(); auto capsule = py::capsule( fsv, [](void *data) { detail::free(data); }); From 4c9b5adf820204af9c043e7e0a042d19f853843d Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 9 Aug 2021 12:43:46 -0700 Subject: [PATCH 032/246] Surface dblocks in simulator options. --- pybind_interface/pybind_main.cpp | 2 +- qsimcirq/qsim_simulator.py | 11 ++++------- qsimcirq_tests/qsimcirq_test.py | 6 +++--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index ed788ee2..288b5b47 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -387,7 +387,7 @@ std::vector> qsim_simulate(const py::dict &options) { Runner::Parameter param; try { num_threads = parseOptions(options, "t\0"); - num_dblocks = 1; + num_dblocks = parseOptions(options, "g\0"); param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); param.seed = parseOptions(options, "s\0"); diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 66c68272..b1f57153 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -93,7 +93,8 @@ def __init__( applied to all circuits run using this simulator. Accepted keys and their behavior are as follows: - 'f': int (> 0). Maximum size of fused gates. Default: 2. - - 'g': bool. If true, simulate with GPU. Default: false. + - 'g': int (>= 0). GPU dblocks to use. If set to zero, + GPUs will not be used for simulation. Default: 0. - 'r': int (> 0). Noisy repetitions (see below). Default: 1. - 't': int (> 0). Number of threads to run on. Default: 1. - 'v': int (>= 0). Log verbosity. Default: 0. @@ -121,13 +122,9 @@ def __init__( ) self._prng = value.parse_random_state(seed) # module to use for simulation - self._sim_module = ( - qsim_gpu - if 'g' in qsim_options and qsim_options['g'] - else qsim - ) - self.qsim_options = {"t": 1, "f": 2, "v": 0, "r": 1} + self.qsim_options = {"t": 1, "g": 0, "f": 2, "v": 0, "r": 1} self.qsim_options.update(qsim_options) + self._sim_module = qsim_gpu if qsim_options['g'] > 0 else qsim # Deque of (, ) tuples. self._translated_circuits = deque(maxlen=circuit_memoization_size) diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 22266f3e..4a3dafb7 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -1070,7 +1070,7 @@ def test_cirq_qsim_gpu_amplitudes(): cirq_circuit = cirq.Circuit(cirq.CNOT(a, b), cirq.CNOT(b, a), cirq.X(a)) # Enable GPU acceleration. - gpu_options = {'g': True} + gpu_options = {'g': 32} qsimGpuSim = qsimcirq.QSimSimulator(qsim_options=gpu_options) result = qsimGpuSim.compute_amplitudes( cirq_circuit, bitstrings=[0b00, 0b01, 0b10, 0b11] @@ -1086,7 +1086,7 @@ def test_cirq_qsim_gpu_simulate(): cirq_circuit = cirq.Circuit(cirq.H(a), cirq.CNOT(a, b), cirq.X(b)) # Enable GPU acceleration. - gpu_options = {'g': True} + gpu_options = {'g': 32} qsimGpuSim = qsimcirq.QSimSimulator(qsim_options=gpu_options) result = qsimGpuSim.simulate(cirq_circuit) assert result.state_vector().shape == (4,) @@ -1107,7 +1107,7 @@ def test_cirq_qsim_gpu_expectation_values(): obs = [cirq.Z(a) * cirq.Z(b)] # Enable GPU acceleration. - gpu_options = {'g': True} + gpu_options = {'g': 32} qsimGpuSim = qsimcirq.QSimSimulator(qsim_options=gpu_options) result = qsimGpuSim.simulate_expectation_values(cirq_circuit, obs) From 720e9f296189ce8b22bcc6538e6e283aeeeea2fd Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 9 Aug 2021 12:46:11 -0700 Subject: [PATCH 033/246] Surface dblocks, part 2. --- pybind_interface/pybind_main.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 288b5b47..8c9ee116 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -431,7 +431,7 @@ std::vector> qtrajectory_simulate(const py::dict &options) { try { num_threads = parseOptions(options, "t\0"); - num_dblocks = 1; + num_dblocks = parseOptions(options, "g\0"); param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); seed = parseOptions(options, "s\0"); @@ -537,7 +537,7 @@ class SimulatorHelper { num_qubits = circuit.num_qubits; } num_threads = parseOptions(options, "t\0"); - num_dblocks = 1; + num_dblocks = parseOptions(options, "g\0"); max_fused_size = parseOptions(options, "f\0"); verbosity = parseOptions(options, "v\0"); seed = parseOptions(options, "s\0"); @@ -756,7 +756,7 @@ std::vector qsim_sample(const py::dict &options) { Runner::Parameter param; try { num_threads = parseOptions(options, "t\0"); - num_dblocks = 1; + num_dblocks = parseOptions(options, "g\0"); param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); param.seed = parseOptions(options, "s\0"); @@ -806,7 +806,7 @@ std::vector qtrajectory_sample(const py::dict &options) { try { num_threads = parseOptions(options, "t\0"); - num_dblocks = 1; + num_dblocks = parseOptions(options, "g\0"); param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); seed = parseOptions(options, "s\0"); From c8e60ca3ff2c868b17396ff0ef0a06bb891840c4 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 9 Aug 2021 13:24:43 -0700 Subject: [PATCH 034/246] Surface dblocks, part 3. --- qsimcirq/qsim_simulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index b1f57153..38f2cbe8 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -124,7 +124,7 @@ def __init__( # module to use for simulation self.qsim_options = {"t": 1, "g": 0, "f": 2, "v": 0, "r": 1} self.qsim_options.update(qsim_options) - self._sim_module = qsim_gpu if qsim_options['g'] > 0 else qsim + self._sim_module = qsim_gpu if self.qsim_options['g'] > 0 else qsim # Deque of (, ) tuples. self._translated_circuits = deque(maxlen=circuit_memoization_size) From 3e8c7b6c41cbad8f2cddc55728abf36c0c8f380d Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Mon, 9 Aug 2021 20:39:55 +0000 Subject: [PATCH 035/246] added autoscaler --- .../terraform/{ => htcondor}/autoscaler.py | 1 + .../multinode/terraform/htcondor/resources.tf | 39 +++++++++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) rename docs/tutorials/multinode/terraform/{ => htcondor}/autoscaler.py (99%) diff --git a/docs/tutorials/multinode/terraform/autoscaler.py b/docs/tutorials/multinode/terraform/htcondor/autoscaler.py similarity index 99% rename from docs/tutorials/multinode/terraform/autoscaler.py rename to docs/tutorials/multinode/terraform/htcondor/autoscaler.py index 8669b7da..68c7a01d 100644 --- a/docs/tutorials/multinode/terraform/autoscaler.py +++ b/docs/tutorials/multinode/terraform/htcondor/autoscaler.py @@ -258,6 +258,7 @@ def main(): scaler = AutoScaler() + exit() # Project ID scaler.project = args.p # Ex:'slurm-var-demo' diff --git a/docs/tutorials/multinode/terraform/htcondor/resources.tf b/docs/tutorials/multinode/terraform/htcondor/resources.tf index ab00a471..cad60189 100644 --- a/docs/tutorials/multinode/terraform/htcondor/resources.tf +++ b/docs/tutorials/multinode/terraform/htcondor/resources.tf @@ -30,7 +30,7 @@ variable "zone" { } variable "min_replicas" { type = number - default = 1 + default = 0 } variable "max_replicas" { type = number @@ -199,7 +199,8 @@ resource "google_compute_instance" "condor-submit" { service_account { email = var.service_account # email = "487217491196-compute@developer.gserviceaccount.com" - scopes = ["https://www.googleapis.com/auth/monitoring.write", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/servicecontrol", "https://www.googleapis.com/auth/devstorage.read_only", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/service.management.readonly", "https://www.googleapis.com/auth/trace.append"] + #scopes = ["https://www.googleapis.com/auth/monitoring.write", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/servicecontrol", "https://www.googleapis.com/auth/devstorage.read_only", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/service.management.readonly", "https://www.googleapis.com/auth/trace.append"] + scopes = ["https://www.googleapis.com/auth/cloud-platform"] } shielded_instance_config { @@ -210,6 +211,17 @@ resource "google_compute_instance" "condor-submit" { tags = ["${var.cluster_name}-submit"] zone = var.zone + provisioner "file" { + source = "autoscaler.py" + destination = "/opt/autoscaler.py" + + #connection { + # type = "ssh" + # user = "jon" + # private_key = "${file("./creds/gcloud_instance")}" + # agent = "false" + #} + } } resource "google_compute_instance_template" "condor-compute" { can_ip_forward = "false" @@ -283,6 +295,7 @@ resource "google_compute_instance_group_manager" "condor-compute-igm" { ] zone = var.zone } +/* resource "google_compute_autoscaler" "condor-compute-as" { name = "${var.cluster_name}-compute-as" project = var.project @@ -298,16 +311,16 @@ resource "google_compute_autoscaler" "condor-compute-as" { target = 0.2 } - # metric { - # name = "custom.googleapis.com/q0" - # target = var.metric_target_queue - # type = "GAUGE" - # } - # metric { - # name = "custom.googleapis.com/la0" - # target = var.metric_target_loadavg - # type = "GAUGE" - # } + metric { + name = "custom.googleapis.com/q0" + target = var.metric_target_queue + type = "GAUGE" + } + metric { + name = "custom.googleapis.com/la0" + target = var.metric_target_loadavg + type = "GAUGE" + } } @@ -320,3 +333,5 @@ resource "google_compute_autoscaler" "condor-compute-as" { google_compute_instance_group_manager.condor-compute-igm ] } +*/ + From 94773c59150f7902d810807b63ec554571f7309d Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Mon, 9 Aug 2021 20:50:46 +0000 Subject: [PATCH 036/246] Creating python vi startup.sh --- .../multinode/terraform/htcondor/resources.tf | 11 - .../terraform/htcondor/startup-centos.sh | 280 +++++++++++++++++- 2 files changed, 279 insertions(+), 12 deletions(-) diff --git a/docs/tutorials/multinode/terraform/htcondor/resources.tf b/docs/tutorials/multinode/terraform/htcondor/resources.tf index cad60189..634a5814 100644 --- a/docs/tutorials/multinode/terraform/htcondor/resources.tf +++ b/docs/tutorials/multinode/terraform/htcondor/resources.tf @@ -211,17 +211,6 @@ resource "google_compute_instance" "condor-submit" { tags = ["${var.cluster_name}-submit"] zone = var.zone - provisioner "file" { - source = "autoscaler.py" - destination = "/opt/autoscaler.py" - - #connection { - # type = "ssh" - # user = "jon" - # private_key = "${file("./creds/gcloud_instance")}" - # agent = "false" - #} - } } resource "google_compute_instance_template" "condor-compute" { can_ip_forward = "false" diff --git a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh index cc5dbedc..1336b547 100644 --- a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh +++ b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh @@ -173,4 +173,282 @@ if [ "$SERVER_TYPE" == "submit" ]; then python3 -m pip install --upgrade oauth2client python3 -m pip install --upgrade google-api-python-client python3 -m pip install --upgrade absl-py -fi \ No newline at end of file +fi + +cat < /opt/autoscaler.py +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright 2018 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Script for resizing managed instance group (MIG) cluster size based +# on the number of jobs in the Condor Queue. + +from pprint import pprint +from googleapiclient import discovery +from oauth2client.client import GoogleCredentials + +import os +import math +import argparse + +parser = argparse.ArgumentParser("autoscaler.py") +parser.add_argument("-p", "--project_id", help="Project id", type=str) +parser.add_argument("-r", "--region", help="GCP region where the managed instance group is located", type=str) +parser.add_argument("-z", "--zone", help="Name of GCP zone where the managed instance group is located", type=str) +parser.add_argument("-g", "--group_manager", help="Name of the managed instance group", type=str) +parser.add_argument("-c", "--computeinstancelimit", help="Maximum number of compute instances", type=int) +parser.add_argument("-v", "--verbosity", help="Increase output verbosity. 1-show basic debug info. 2-show detail debug info", type=int, choices=[0, 1, 2]) +args = parser.parse_args() + +# Project ID +project = args.project_id # Ex:'slurm-var-demo' + +# Region where the managed instance group is located +region = args.region # Ex: 'us-central1' + +# Name of the zone where the managed instance group is located +zone = args.zone # Ex: 'us-central1-f' + +# The name of the managed instance group. +instance_group_manager = args.group_manager # Ex: 'condor-compute-igm' + +# Default number of cores per intance, will be replaced with actual value +cores_per_node = 4 + +# Default number of running instances that the managed instance group should maintain at any given time. This number will go up and down based on the load (number of jobs in the queue) +size = 0 + +# Debug level: 1-print debug information, 2 - print detail debug information +debug = 0 +if (args.verbosity): + debug = args.verbosity + +# Limit for the maximum number of compute instance. If zero (default setting), no limit will be enforced by the script +compute_instance_limit = 0 +if (args.computeinstancelimit): + compute_instance_limit = abs(args.computeinstancelimit) + + +if debug > 1: + print('Launching autoscaler.py with the following arguments:') + print('project_id: ' + project) + print('region: ' + region) + print('zone: ' + zone) + print('group_manager: ' + instance_group_manager) + print('computeinstancelimit: ' + str(compute_instance_limit)) + print('debuglevel: ' + str(debug)) + + +# Remove specified instance from MIG and decrease MIG size +def deleteFromMig(instance): + instanceUrl = 'https://www.googleapis.com/compute/v1/projects/' \ + + project + '/zones/' + zone + '/instances/' + instance + instances_to_delete = {'instances': [instanceUrl]} + + requestDelInstance = \ + service.instanceGroupManagers().deleteInstances(project=project, + zone=zone, instanceGroupManager=instance_group_manager, + body=instances_to_delete) + response = requestDelInstance.execute() + if debug > 0: + print('Request to delete instance ' + instance) + pprint(response) + + return response + +def getInstanceTemplateInfo(): + requestTemplateName = \ + service.instanceGroupManagers().get(project=project, zone=zone, + instanceGroupManager=instance_group_manager, + fields='instanceTemplate') + responseTemplateName = requestTemplateName.execute() + template_name = '' + + if debug > 1: + print('Request for the template name') + pprint(responseTemplateName) + + if len(responseTemplateName) > 0: + template_url = responseTemplateName.get('instanceTemplate') + template_url_partitioned = template_url.split('/') + template_name = \ + template_url_partitioned[len(template_url_partitioned) - 1] + + requestInstanceTemplate = \ + service.instanceTemplates().get(project=project, + instanceTemplate=template_name, fields='properties') + responseInstanceTemplateInfo = requestInstanceTemplate.execute() + + if debug > 1: + print('Template information') + pprint(responseInstanceTemplateInfo['properties']) + + machine_type = responseInstanceTemplateInfo['properties']['machineType'] + is_preemtible = responseInstanceTemplateInfo['properties']['scheduling']['preemptible'] + if debug > 0: + print('Machine Type: ' + machine_type) + print('Is preemtible: ' + str(is_preemtible)) + request = service.machineTypes().get(project=project, zone=zone, + machineType=machine_type) + response = request.execute() + guest_cpus = response['guestCpus'] + if debug > 1: + print('Machine information') + pprint(responseInstanceTemplateInfo['properties']) + if debug > 0: + print('Guest CPUs: ' + str(guest_cpus)) + + instanceTemlateInfo = {'machine_type': machine_type, + 'is_preemtible': is_preemtible, + 'guest_cpus': guest_cpus} + return instanceTemlateInfo + + +# Obtain credentials +credentials = GoogleCredentials.get_application_default() +service = discovery.build('compute', 'v1', credentials=credentials) + +# Get total number of jobs in the queue that includes number of jos waiting as well as number of jobs already assigned to nodes +queue_length_req = 'condor_q -totals -format "%d " Jobs -format "%d " Idle -format "%d " Held' +queue_length_resp = os.popen(queue_length_req).read().split() + +if len(queue_length_resp) > 1: + queue = int(queue_length_resp[0]) + idle_jobs = int(queue_length_resp[1]) + on_hold_jobs = int(queue_length_resp[2]) +else: + queue = 0 + idle_jobs = 0 + on_hold_jobs = 0 + +print('Total queue length: ' + str(queue)) +print('Idle jobs: ' + str(idle_jobs)) +print('Jobs on hold: ' + str(on_hold_jobs)) + +instanceTemlateInfo = getInstanceTemplateInfo() +if debug > 1: + print('Information about the compute instance template') + pprint(instanceTemlateInfo) + +cores_per_node = instanceTemlateInfo['guest_cpus'] +print('Number of CPU per compute node: ' + str(cores_per_node)) + +# Get state for for all jobs in Condor +name_req = 'condor_status -af name state' +slot_names = os.popen(name_req).read().splitlines() +if debug > 1: + print('Currently running jobs in Condor') + print(slot_names) + +# Adjust current queue length by the number of jos that are on-hold +queue -=on_hold_jobs +if on_hold_jobs>0: + print("Adjusted queue length: " + str(queue)) + +# Calculate number instances to satisfy current job queue length +if queue > 0: + size = int(math.ceil(float(queue) / float(cores_per_node))) + if debug>0: + print("Calucalting size of MIG: ⌈" + str(queue) + "/" + str(cores_per_node) + "⌉ = " + str(size)) +else: + size = 0 + +# If compute instance limit is specified, can not start more instances then specified in the limit +if compute_instance_limit > 0 and size > compute_instance_limit: + size = compute_instance_limit; + print("MIG target size will be limited by " + str(compute_instance_limit)) + +print('New MIG target size: ' + str(size)) + +# Get current number of instances in the MIG +requestGroupInfo = service.instanceGroupManagers().get(project=project, + zone=zone, instanceGroupManager=instance_group_manager) +responseGroupInfo = requestGroupInfo.execute() +currentTarget = int(responseGroupInfo['targetSize']) +print('Current MIG target size: ' + str(currentTarget)) + +if debug > 1: + print('MIG Information:') + print(responseGroupInfo) + +if size == 0 and currentTarget == 0: + print('No jobs in the queue and no compute instances running. Nothing to do') + exit() + +if size == currentTarget: + print('Running correct number of compute nodes to handle number of jobs in the queue') + exit() + + +if size < currentTarget: + print('Scaling down. Looking for nodes that can be shut down' ) + # Find nodes that are not busy (all slots showing status as "Unclaimed") + + node_busy = {} + for slot_name in slot_names: + name_status = slot_name.split() + if len(name_status) > 1: + name = name_status[0] + status = name_status[1] + slot = "NO-SLOT" + slot_server = name.split('@') + if len(slot_server) > 1: + slot = slot_server[0] + server = slot_server[1].split('.')[0] + else: + server = slot_server[0].split('.')[0] + + if debug > 0: + print(slot + ', ' + server + ', ' + status + '\n') + + if server not in node_busy: + if status == 'Unclaimed': + node_busy[server] = False + else: + node_busy[server] = True + else: + if status != 'Unclaimed': + node_busy[server] = True + + if debug > 1: + print('Compuute node busy status:') + print(node_busy) + + # Shut down nodes that are not busy + for node in node_busy: + if not node_busy[node]: + print('Will shut down: ' + node + ' ...') + respDel = deleteFromMig(node) + if debug > 1: + print("Shut down request for compute node " + node) + pprint(respDel) + + if debug > 1: + print("Scaling down complete") + +if size > currentTarget: + print("Scaling up. Need to increase number of instances to " + str(size)) + #Request to resize + request = service.instanceGroupManagers().resize(project=project, + zone=zone, + instanceGroupManager=instance_group_manager, + size=size) + response = request.execute() + if debug > 1: + print('Requesting to increase MIG size') + pprint(response) + print("Scaling up complete") +EOFZ From b6cfa1fedfd07ddeec6ea382ad6643e549e5f22f Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Mon, 9 Aug 2021 21:06:51 +0000 Subject: [PATCH 037/246] Set min num nodes to 0 --- docs/tutorials/multinode/terraform/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/multinode/terraform/main.tf b/docs/tutorials/multinode/terraform/main.tf index 72ac2f8d..c883028c 100644 --- a/docs/tutorials/multinode/terraform/main.tf +++ b/docs/tutorials/multinode/terraform/main.tf @@ -18,7 +18,7 @@ module "htcondor" { zone = var.zone osversion = "7" max_replicas=20 - min_replicas=1 + min_replicas=0 service_account="htcondor@${var.project}.iam.gserviceaccount.com" use_preemptibles=false osproject ="centos-cloud" From 805837b836f71b3f53631e780ff3625bf51c2ede Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 13 Aug 2021 09:00:32 -0700 Subject: [PATCH 038/246] Overhaul qsim options. --- pybind_interface/avx2/pybind_main_avx2.cpp | 9 +- .../avx512/pybind_main_avx512.cpp | 9 +- pybind_interface/basic/pybind_main_basic.cpp | 9 +- pybind_interface/cuda/pybind_main_cuda.cpp | 8 +- pybind_interface/pybind_main.cpp | 120 +++++++++++++----- pybind_interface/sse/pybind_main_sse.cpp | 9 +- qsimcirq/__init__.py | 7 +- qsimcirq/qsim_simulator.py | 83 +++++++++--- qsimcirq_tests/qsimcirq_test.py | 18 ++- 9 files changed, 201 insertions(+), 71 deletions(-) diff --git a/pybind_interface/avx2/pybind_main_avx2.cpp b/pybind_interface/avx2/pybind_main_avx2.cpp index 54ad2d30..5729d163 100644 --- a/pybind_interface/avx2/pybind_main_avx2.cpp +++ b/pybind_interface/avx2/pybind_main_avx2.cpp @@ -22,9 +22,12 @@ namespace qsim { using Simulator = SimulatorAVX; struct Factory { - // num_dblocks is unused, but kept for consistency with GPU version. - Factory(unsigned num_threads, unsigned num_dblocks) - : num_threads(num_threads) {} + // num_state_threads and num_dblocks are unused, but kept for consistency + // with the GPU Factory. + Factory( + unsigned num_sim_threads, + unsigned num_state_threads, + unsigned num_dblocks) : num_threads(num_sim_threads) {} using Simulator = qsim::Simulator; using StateSpace = Simulator::StateSpace; diff --git a/pybind_interface/avx512/pybind_main_avx512.cpp b/pybind_interface/avx512/pybind_main_avx512.cpp index 996de2c6..0073a1e0 100644 --- a/pybind_interface/avx512/pybind_main_avx512.cpp +++ b/pybind_interface/avx512/pybind_main_avx512.cpp @@ -22,9 +22,12 @@ namespace qsim { using Simulator = SimulatorAVX512; struct Factory { - // num_dblocks is unused, but kept for consistency with GPU version. - Factory(unsigned num_threads, unsigned num_dblocks) - : num_threads(num_threads) {} + // num_state_threads and num_dblocks are unused, but kept for consistency + // with the GPU Factory. + Factory( + unsigned num_sim_threads, + unsigned num_state_threads, + unsigned num_dblocks) : num_threads(num_sim_threads) {} using Simulator = qsim::Simulator; using StateSpace = Simulator::StateSpace; diff --git a/pybind_interface/basic/pybind_main_basic.cpp b/pybind_interface/basic/pybind_main_basic.cpp index 244688b5..55cb5484 100644 --- a/pybind_interface/basic/pybind_main_basic.cpp +++ b/pybind_interface/basic/pybind_main_basic.cpp @@ -22,9 +22,12 @@ namespace qsim { using Simulator = SimulatorBasic; struct Factory { - // num_dblocks is unused, but kept for consistency with GPU version. - Factory(unsigned num_threads, unsigned num_dblocks) - : num_threads(num_threads) {} + // num_state_threads and num_dblocks are unused, but kept for consistency + // with the GPU Factory. + Factory( + unsigned num_sim_threads, + unsigned num_state_threads, + unsigned num_dblocks) : num_threads(num_sim_threads) {} using Simulator = qsim::Simulator; using StateSpace = Simulator::StateSpace; diff --git a/pybind_interface/cuda/pybind_main_cuda.cpp b/pybind_interface/cuda/pybind_main_cuda.cpp index b55d0d65..825082f5 100644 --- a/pybind_interface/cuda/pybind_main_cuda.cpp +++ b/pybind_interface/cuda/pybind_main_cuda.cpp @@ -23,8 +23,12 @@ namespace qsim { using Simulator = qsim::Simulator; using StateSpace = Simulator::StateSpace; - Factory(unsigned num_threads, unsigned num_dblocks) - : ss_params{num_threads, num_dblocks}, sim_params{num_threads} {} + Factory( + unsigned num_sim_threads, + unsigned num_state_threads, + unsigned num_dblocks + ) : ss_params{num_state_threads, num_dblocks}, + sim_params{num_sim_threads} {} StateSpace CreateStateSpace() const { return StateSpace(ss_params); diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 8c9ee116..2a28d1ac 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -382,12 +382,20 @@ std::vector> qsim_simulate(const py::dict &options) { using Runner = QSimRunner>, Factory>; - unsigned num_threads; - unsigned num_dblocks; + bool use_gpu; + unsigned num_sim_threads; + unsigned num_state_threads = 0; + unsigned num_dblocks = 0; Runner::Parameter param; try { - num_threads = parseOptions(options, "t\0"); - num_dblocks = parseOptions(options, "g\0"); + use_gpu = parseOptions(options, "g\0"); + if (use_gpu) { + num_sim_threads = parseOptions(options, "gsmt\0"); + num_state_threads = parseOptions(options, "gsst\0"); + num_dblocks = parseOptions(options, "gdb\0"); + } else { + num_sim_threads = parseOptions(options, "t\0"); + } param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); param.seed = parseOptions(options, "s\0"); @@ -395,7 +403,9 @@ std::vector> qsim_simulate(const py::dict &options) { IO::errorf(exp.what()); return {}; } - Runner::Run(param, Factory(num_threads, num_dblocks), circuit, measure); + Runner::Run( + param, Factory(num_sim_threads, num_state_threads, num_dblocks), circuit, + measure); return amplitudes; } @@ -425,13 +435,21 @@ std::vector> qtrajectory_simulate(const py::dict &options) { Simulator>; Runner::Parameter param; - unsigned num_threads; - unsigned num_dblocks; + bool use_gpu; + unsigned num_sim_threads; + unsigned num_state_threads = 0; + unsigned num_dblocks = 0; uint64_t seed; try { - num_threads = parseOptions(options, "t\0"); - num_dblocks = parseOptions(options, "g\0"); + use_gpu = parseOptions(options, "g\0"); + if (use_gpu) { + num_sim_threads = parseOptions(options, "gsmt\0"); + num_state_threads = parseOptions(options, "gsst\0"); + num_dblocks = parseOptions(options, "gdb\0"); + } else { + num_sim_threads = parseOptions(options, "t\0"); + } param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); seed = parseOptions(options, "s\0"); @@ -440,8 +458,10 @@ std::vector> qtrajectory_simulate(const py::dict &options) { return {}; } - Simulator simulator = Factory(num_threads, num_dblocks).CreateSimulator(); - StateSpace state_space = Factory(num_threads, num_dblocks).CreateStateSpace(); + Simulator simulator = Factory( + num_sim_threads, num_state_threads, num_dblocks).CreateSimulator(); + StateSpace state_space = Factory( + num_sim_threads, num_state_threads, num_dblocks).CreateStateSpace(); auto measure = [&bitstrings, &ncircuit, &litudes, &state_space]( unsigned k, const State &state, @@ -522,7 +542,7 @@ class SimulatorHelper { private: SimulatorHelper(const py::dict &options, bool noisy) - : state_space(Factory(1, 1).CreateStateSpace()), + : state_space(Factory(1, 1, 1).CreateStateSpace()), state(StateSpace::Null()), scratch(StateSpace::Null()) { is_valid = false; @@ -536,13 +556,21 @@ class SimulatorHelper { circuit = getCircuit(options); num_qubits = circuit.num_qubits; } - num_threads = parseOptions(options, "t\0"); - num_dblocks = parseOptions(options, "g\0"); + + use_gpu = parseOptions(options, "g\0"); + if (use_gpu) { + num_sim_threads = parseOptions(options, "gsmt\0"); + num_state_threads = parseOptions(options, "gsst\0"); + num_dblocks = parseOptions(options, "gdb\0"); + } else { + num_sim_threads = parseOptions(options, "t\0"); + } max_fused_size = parseOptions(options, "f\0"); verbosity = parseOptions(options, "v\0"); seed = parseOptions(options, "s\0"); - state_space = Factory(num_threads, num_dblocks).CreateStateSpace(); + state_space = Factory( + num_sim_threads, num_state_threads, num_dblocks).CreateStateSpace(); state = state_space.Create(num_qubits); is_valid = true; } catch (const std::invalid_argument &exp) { @@ -562,7 +590,7 @@ class SimulatorHelper { float* fsv) { fsv[i] = ptr[i]; }; - For(num_threads).Run(input_vector.size(), f, ptr, state.get()); + For(num_sim_threads).Run(input_vector.size(), f, ptr, state.get()); state_space.NormalToInternalOrder(state); } @@ -589,14 +617,18 @@ class SimulatorHelper { std::vector stat; auto params = get_noisy_params(); - Simulator simulator = Factory(num_threads, num_dblocks).CreateSimulator(); - StateSpace state_space = Factory(num_threads, num_dblocks).CreateStateSpace(); + Simulator simulator = Factory( + num_sim_threads, num_state_threads, num_dblocks).CreateSimulator(); + StateSpace state_space = Factory( + num_sim_threads, num_state_threads, num_dblocks).CreateStateSpace(); result = NoisyRunner::RunOnce(params, ncircuit, seed, state_space, simulator, scratch, state, stat); } else { result = Runner::Run( - get_params(), Factory(num_threads, num_dblocks), circuit, state); + get_params(), + Factory(num_sim_threads, num_state_threads, num_dblocks), + circuit, state); } seed += 1; return result; @@ -629,7 +661,8 @@ class SimulatorHelper { std::vector> get_expectation_value( const std::vector>, unsigned>>& opsums_and_qubit_counts) { - Simulator simulator = Factory(num_threads, num_dblocks).CreateSimulator(); + Simulator simulator = Factory( + num_sim_threads, num_state_threads, num_dblocks).CreateSimulator(); using Fuser = MultiQubitGateFuser; std::vector> results; @@ -657,8 +690,10 @@ class SimulatorHelper { State state; State scratch; + bool use_gpu; unsigned num_qubits; - unsigned num_threads; + unsigned num_sim_threads; + unsigned num_state_threads; unsigned num_dblocks; unsigned noisy_reps; unsigned max_fused_size; @@ -751,12 +786,20 @@ std::vector qsim_sample(const py::dict &options) { using Runner = QSimRunner>, Factory>; - unsigned num_threads; - unsigned num_dblocks; + bool use_gpu; + unsigned num_sim_threads; + unsigned num_state_threads = 0; + unsigned num_dblocks = 0; Runner::Parameter param; try { - num_threads = parseOptions(options, "t\0"); - num_dblocks = parseOptions(options, "g\0"); + use_gpu = parseOptions(options, "g\0"); + if (use_gpu) { + num_sim_threads = parseOptions(options, "gsmt\0"); + num_state_threads = parseOptions(options, "gsst\0"); + num_dblocks = parseOptions(options, "gdb\0"); + } else { + num_sim_threads = parseOptions(options, "t\0"); + } param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); param.seed = parseOptions(options, "s\0"); @@ -766,11 +809,14 @@ std::vector qsim_sample(const py::dict &options) { } std::vector results; - StateSpace state_space = Factory(num_threads, num_dblocks).CreateStateSpace(); + StateSpace state_space = Factory( + num_sim_threads, num_state_threads, num_dblocks).CreateStateSpace(); State state = state_space.Create(circuit.num_qubits); state_space.SetStateZero(state); - if (!Runner::Run(param, Factory(num_threads, num_dblocks), circuit, state, results)) { + if (!Runner::Run( + param, Factory(num_sim_threads, num_state_threads, num_dblocks), + circuit, state, results)) { IO::errorf("qsim sampling of the circuit errored out.\n"); return {}; } @@ -800,13 +846,21 @@ std::vector qtrajectory_sample(const py::dict &options) { Simulator>; Runner::Parameter param; - unsigned num_threads; + bool use_gpu; + unsigned num_sim_threads; + unsigned num_state_threads; unsigned num_dblocks; uint64_t seed; try { - num_threads = parseOptions(options, "t\0"); - num_dblocks = parseOptions(options, "g\0"); + use_gpu = parseOptions(options, "g\0"); + if (use_gpu) { + num_sim_threads = parseOptions(options, "gsmt\0"); + num_state_threads = parseOptions(options, "gsst\0"); + num_dblocks = parseOptions(options, "gdb\0"); + } else { + num_sim_threads = parseOptions(options, "t\0"); + } param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); seed = parseOptions(options, "s\0"); @@ -816,8 +870,10 @@ std::vector qtrajectory_sample(const py::dict &options) { return {}; } - Simulator simulator = Factory(num_threads, num_dblocks).CreateSimulator(); - StateSpace state_space = Factory(num_threads, num_dblocks).CreateStateSpace(); + Simulator simulator = Factory( + num_sim_threads, num_state_threads, num_dblocks).CreateSimulator(); + StateSpace state_space = Factory( + num_sim_threads, num_state_threads, num_dblocks).CreateStateSpace(); std::vector> results; diff --git a/pybind_interface/sse/pybind_main_sse.cpp b/pybind_interface/sse/pybind_main_sse.cpp index 21490f68..4c72a65a 100644 --- a/pybind_interface/sse/pybind_main_sse.cpp +++ b/pybind_interface/sse/pybind_main_sse.cpp @@ -22,9 +22,12 @@ namespace qsim { using Simulator = SimulatorSSE; struct Factory { - // num_dblocks is unused, but kept for consistency with GPU version. - Factory(unsigned num_threads, unsigned num_dblocks) - : num_threads(num_threads) {} + // num_state_threads and num_dblocks are unused, but kept for consistency + // with the GPU Factory. + Factory( + unsigned num_sim_threads, + unsigned num_state_threads, + unsigned num_dblocks) : num_threads(num_sim_threads) {} using Simulator = qsim::Simulator; using StateSpace = Simulator::StateSpace; diff --git a/qsimcirq/__init__.py b/qsimcirq/__init__.py index 687d899a..f1542018 100644 --- a/qsimcirq/__init__.py +++ b/qsimcirq/__init__.py @@ -28,7 +28,12 @@ def _load_qsim_gpu(): qsim_gpu = _load_qsim_gpu() from .qsim_circuit import add_op_to_opstring, add_op_to_circuit, QSimCircuit -from .qsim_simulator import QSimSimulatorState, QSimSimulatorTrialResult, QSimSimulator +from .qsim_simulator import ( + QSimOptions, + QSimSimulatorState, + QSimSimulatorTrialResult, + QSimSimulator, +) from .qsimh_simulator import QSimhSimulator from qsimcirq._version import ( diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 38f2cbe8..63ab5998 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -13,6 +13,7 @@ # limitations under the License. from collections import deque +from dataclasses import dataclass from typing import Any, Dict, List, Optional, Sequence, Tuple, Union from cirq import ( @@ -74,6 +75,59 @@ def _needs_trajectories(circuit: circuits.Circuit) -> bool: return False +@dataclass +class QSimOptions: + """Container for options to the QSimSimulator. + + Options for the simulator can also be provided as a {string: value} dict, + using the format shown in the 'as_dict' function for this class. + + Args: + max_fused_gate_size: maximum number of qubits allowed per fused gate. + Depending on the capabilities of the device qsim runs on, this + usually has best performance when set to 3 or 4. + cpu_threads: number of threads to use when running on CPU. For best + performance, this should equal the number of cores on the device. + ev_noisy_repetitions: number of repetitions used for estimating + expectation values of a noisy circuit. Does not affect other + simulation modes. + use_gpu: whether to use GPU instead of CPU for simulation. The "gpu_*" + arguments below are only considered if this is set to True. + gpu_sim_threads: number of threads to use for the GPU Simulator. + This must be a power of 2 in the range [32, 256]. + gpu_state_threads: number of threads to use for the GPU StateSpace. + This must be a power of 2 in the range [32, 1024]. + gpu_data_blocks: number of data blocks to use on GPU. Below 16 data + blocks, performance is noticeably reduced. + verbosity: Logging verbosity. + """ + + max_fused_gate_size: int = 2 + cpu_threads: int = 1 + ev_noisy_repetitions: int = 1 + use_gpu: bool = False + gpu_sim_threads: int = 32 + gpu_state_threads: int = 32 + gpu_data_blocks: int = 16 + verbosity: int = 0 + + def as_dict(self): + """Generates an options dict from this object. + + Options to QSimSimulator can also be provided in this format directly. + """ + return { + "f": self.max_fused_gate_size, + "t": self.cpu_threads, + "r": self.ev_noisy_repetitions, + "g": self.use_gpu, + "gsmt": self.gpu_sim_threads, + "gsst": self.gpu_state_threads, + "gdb": self.gpu_data_blocks, + "v": self.verbosity, + } + + class QSimSimulator( SimulatesSamples, SimulatesAmplitudes, @@ -82,26 +136,16 @@ class QSimSimulator( ): def __init__( self, - qsim_options: dict = {}, + qsim_options: Union[None, Dict, QSimOptions] = None, seed: value.RANDOM_STATE_OR_SEED_LIKE = None, circuit_memoization_size: int = 0, ): """Creates a new QSimSimulator using the given options and seed. Args: - qsim_options: A map of circuit options for the simulator. These will be - applied to all circuits run using this simulator. Accepted keys and - their behavior are as follows: - - 'f': int (> 0). Maximum size of fused gates. Default: 2. - - 'g': int (>= 0). GPU dblocks to use. If set to zero, - GPUs will not be used for simulation. Default: 0. - - 'r': int (> 0). Noisy repetitions (see below). Default: 1. - - 't': int (> 0). Number of threads to run on. Default: 1. - - 'v': int (>= 0). Log verbosity. Default: 0. - See qsim/docs/usage.md for more details on these options. - "Noisy repetitions" specifies how many repetitions to aggregate - over when calculating expectation values for a noisy circuit. - Note that this does not apply to other simulation types. + qsim_options: An options dict or QSimOptions object with options + to use for all circuits run using this simulator. See the + QSimOptions class for details. seed: A random state or seed object, as defined in cirq.value. circuit_memoization_size: The number of last translated circuits to be memoized from simulation executions, to eliminate @@ -115,16 +159,21 @@ def __init__( Raises: ValueError if internal keys 'c', 'i' or 's' are included in 'qsim_options'. """ + if isinstance(qsim_options, QSimOptions): + qsim_options = qsim_options.as_dict() + else: + qsim_options = qsim_options or {} + if any(k in qsim_options for k in ("c", "i", "s")): raise ValueError( 'Keys {"c", "i", "s"} are reserved for internal use and cannot be ' "used in QSimCircuit instantiation." ) self._prng = value.parse_random_state(seed) - # module to use for simulation - self.qsim_options = {"t": 1, "g": 0, "f": 2, "v": 0, "r": 1} + self.qsim_options = QSimOptions().as_dict() self.qsim_options.update(qsim_options) - self._sim_module = qsim_gpu if self.qsim_options['g'] > 0 else qsim + # module to use for simulation + self._sim_module = qsim_gpu if self.qsim_options["g"] > 0 else qsim # Deque of (, ) tuples. self._translated_circuits = deque(maxlen=circuit_memoization_size) diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 4a3dafb7..da6a87a3 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -976,7 +976,8 @@ def test_noise_aggregation(): # Test expectation value aggregation over repetitions of a noisy circuit. # Repetitions are handled in C++, so overhead costs are minimal. - qsim_simulator = qsimcirq.QSimSimulator(qsim_options={"r": 10000}, seed=1) + qsim_options = qsimcirq.QSimOptions(ev_noisy_repetitions=10000) + qsim_simulator = qsimcirq.QSimSimulator(qsim_options=qsim_options, seed=1) qsim_evs = qsim_simulator.simulate_expectation_values(circuit, [psum1, psum2]) assert len(qsim_evs) == 2 @@ -1008,10 +1009,12 @@ def test_multi_qubit_fusion(): cirq.Y(q1) ** 0.5, ) - qsimSim = qsimcirq.QSimSimulator(qsim_options={"f": 2}) + options = qsimcirq.QSimOptions(max_fused_gate_size=2) + qsimSim = qsimcirq.QSimSimulator(qsim_options=options) result_2q_fusion = qsimSim.simulate(cirq_circuit, qubit_order=qubits) - qsimSim = qsimcirq.QSimSimulator(qsim_options={"f": 4}) + options.max_fused_gate_size = 4 + qsimSim = qsimcirq.QSimSimulator(qsim_options=options) result_4q_fusion = qsimSim.simulate(cirq_circuit, qubit_order=qubits) assert cirq.linalg.allclose_up_to_global_phase( result_2q_fusion.state_vector(), result_4q_fusion.state_vector() @@ -1022,7 +1025,8 @@ def test_multi_qubit_fusion(): def test_cirq_qsim_simulate_random_unitary(mode: str): q0, q1 = cirq.LineQubit.range(2) - qsimSim = qsimcirq.QSimSimulator(qsim_options={"t": 16, "v": 0}) + options = qsimcirq.QSimOptions(cpu_threads=16, verbosity=0) + qsimSim = qsimcirq.QSimSimulator(qsim_options=options) for iter in range(10): random_circuit = cirq.testing.random_circuit( qubits=[q0, q1], n_moments=8, op_density=0.99, random_state=iter @@ -1070,7 +1074,7 @@ def test_cirq_qsim_gpu_amplitudes(): cirq_circuit = cirq.Circuit(cirq.CNOT(a, b), cirq.CNOT(b, a), cirq.X(a)) # Enable GPU acceleration. - gpu_options = {'g': 32} + gpu_options = qsimcirq.QSimOptions(use_gpu=True) qsimGpuSim = qsimcirq.QSimSimulator(qsim_options=gpu_options) result = qsimGpuSim.compute_amplitudes( cirq_circuit, bitstrings=[0b00, 0b01, 0b10, 0b11] @@ -1086,7 +1090,7 @@ def test_cirq_qsim_gpu_simulate(): cirq_circuit = cirq.Circuit(cirq.H(a), cirq.CNOT(a, b), cirq.X(b)) # Enable GPU acceleration. - gpu_options = {'g': 32} + gpu_options = qsimcirq.QSimOptions(use_gpu=True) qsimGpuSim = qsimcirq.QSimSimulator(qsim_options=gpu_options) result = qsimGpuSim.simulate(cirq_circuit) assert result.state_vector().shape == (4,) @@ -1107,7 +1111,7 @@ def test_cirq_qsim_gpu_expectation_values(): obs = [cirq.Z(a) * cirq.Z(b)] # Enable GPU acceleration. - gpu_options = {'g': 32} + gpu_options = qsimcirq.QSimOptions(use_gpu=True) qsimGpuSim = qsimcirq.QSimSimulator(qsim_options=gpu_options) result = qsimGpuSim.simulate_expectation_values(cirq_circuit, obs) From 7a8b96bc746b1f5ecde3144601f2c014114d0cc6 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 13 Aug 2021 09:20:24 -0700 Subject: [PATCH 039/246] Apply recommended pybind_main cleanup --- pybind_interface/pybind_main.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 2a28d1ac..f3884a73 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -638,17 +638,11 @@ class SimulatorHelper { state_space.InternalToNormalOrder(state); uint64_t fsv_size = 2 * (uint64_t{1} << num_qubits); if (state.requires_copy_to_host()) { - uint64_t storage_size = state_space.MinSize(state.num_qubits()); - auto* fsv = new float[fsv_size]; - if (storage_size != fsv_size) { - auto* temp = new float[storage_size]; - state_space.Copy(state, temp); - memcpy(fsv, temp, fsv_size * sizeof(float)); - delete [] temp; - } else { - state_space.Copy(state, fsv); - } - auto capsule = py::capsule(fsv, [](void *data) {}); + auto* fsv = new float[state_space.MinSize(state.num_qubits())]; + state_space.Copy(state, fsv); + // Cast on delete to silence warnings. + auto capsule = py::capsule( + fsv, [](void *data) { delete [] (float*)data; }); return py::array_t(fsv_size, fsv, capsule); } else { float* fsv = state.release(); From f00327635d647944529bc1c48e9399dea9d44f03 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 13 Aug 2021 09:26:40 -0700 Subject: [PATCH 040/246] Initialize before use. --- pybind_interface/pybind_main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index f3884a73..7bde45de 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -842,8 +842,8 @@ std::vector qtrajectory_sample(const py::dict &options) { Runner::Parameter param; bool use_gpu; unsigned num_sim_threads; - unsigned num_state_threads; - unsigned num_dblocks; + unsigned num_state_threads = 0; + unsigned num_dblocks = 0; uint64_t seed; try { From 05e4260fda91fae77f4cbb1ba239a3720760ac36 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 13 Aug 2021 11:38:27 -0700 Subject: [PATCH 041/246] WIP GPU-less support --- pybind_interface/Makefile | 4 +++- pybind_interface/decide/decide.cpp | 10 +++++++++- qsimcirq/qsim_simulator.py | 7 ++++++- qsimcirq_tests/qsimcirq_test.py | 18 ++++++++++++++++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/pybind_interface/Makefile b/pybind_interface/Makefile index 89ea0607..ce2b0b5d 100644 --- a/pybind_interface/Makefile +++ b/pybind_interface/Makefile @@ -22,7 +22,9 @@ pybind: $(CXX) sse/pybind_main_sse.cpp -o $(QSIMLIB_SSE) $(CXXFLAGS) $(PYBINDFLAGS_SSE) $(CXX) avx2/pybind_main_avx2.cpp -o $(QSIMLIB_AVX2) $(CXXFLAGS) $(PYBINDFLAGS_AVX2) $(CXX) avx512/pybind_main_avx512.cpp -o $(QSIMLIB_AVX512) $(CXXFLAGS) $(PYBINDFLAGS_AVX512) - $(NVCC) cuda/pybind_main_cuda.cpp -o $(QSIMLIB_CUDA) $(NVCCFLAGS) $(PYBINDFLAGS_CUDA) + if [ -n "$(which $NVCC)" ] ; then\ + $(NVCC) cuda/pybind_main_cuda.cpp -o $(QSIMLIB_CUDA) $(NVCCFLAGS) $(PYBINDFLAGS_CUDA);\ + fi $(CXX) decide/decide.cpp -o $(QSIMLIB_DECIDE) $(CXXFLAGS) $(PYBINDFLAGS_BASIC) .PHONY: clean diff --git a/pybind_interface/decide/decide.cpp b/pybind_interface/decide/decide.cpp index 1355b11c..937c24ac 100644 --- a/pybind_interface/decide/decide.cpp +++ b/pybind_interface/decide/decide.cpp @@ -47,9 +47,17 @@ int detect_instructions() { enum GPUCapabilities { CUDA = 0, NO_GPU = 10 }; +// For now, GPU detection is performed at compile time, as our wheels are +// generated on Github Actions runners which do not have GPU support. +// +// Users wishing to use qsim with GPU will need to compile locally on a device +// which has the necessary CUDA toolkit. int detect_gpu() { - // TODO: detect non-CUDA + #ifdef __NVCC__ GPUCapabilities gpu = CUDA; + #else + GPUCapabilities gpu = NO_GPU; + #endif return gpu; } diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 63ab5998..ed8af1db 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -173,7 +173,12 @@ def __init__( self.qsim_options = QSimOptions().as_dict() self.qsim_options.update(qsim_options) # module to use for simulation - self._sim_module = qsim_gpu if self.qsim_options["g"] > 0 else qsim + if self.qsim_options["g"] and qsim_gpu is None: + raise ValueError( + "GPU execution requested, but not supported. If your device " + "has GPU support, you may need to compile qsim locally." + ) + self._sim_module = qsim_gpu if self.qsim_options["g"] else qsim # Deque of (, ) tuples. self._translated_circuits = deque(maxlen=circuit_memoization_size) diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index da6a87a3..a1fdfe49 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -1066,7 +1066,21 @@ def test_cirq_qsimh_simulate(): assert np.allclose(result, [0j, 0j, (1 + 0j), 0j]) +def test_qsim_gpu_unavailable(): + if qsimcirq.qsim_gpu is not None: + pytest.skip("GPU is available; skipping test.") + + # Attempt to create a simulator with GPU support. + gpu_options = qsimcirq.QSimOptions(use_gpu=True) + with pytest.raises( + ValueError, match="GPU execution requested, but not supported", + ): + _ = qsimcirq.QSimSimulator(qsim_options=gpu_options) + + def test_cirq_qsim_gpu_amplitudes(): + if qsimcirq.qsim_gpu is None: + pytest.skip("GPU is not available for testing.") # Pick qubits. a, b = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] @@ -1083,6 +1097,8 @@ def test_cirq_qsim_gpu_amplitudes(): def test_cirq_qsim_gpu_simulate(): + if qsimcirq.qsim_gpu is None: + pytest.skip("GPU is not available for testing.") # Pick qubits. a, b = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] @@ -1103,6 +1119,8 @@ def test_cirq_qsim_gpu_simulate(): def test_cirq_qsim_gpu_expectation_values(): + if qsimcirq.qsim_gpu is None: + pytest.skip("GPU is not available for testing.") # Pick qubits. a, b = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] From c9febcc586217388ac717cf5664bbe0226a3111b Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 13 Aug 2021 11:46:39 -0700 Subject: [PATCH 042/246] Fill extra factory params --- pybind_interface/pybind_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 41d4ebd0..1d3b951f 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -948,7 +948,7 @@ std::vector> qsimh_simulate(const py::dict &options) { // Define container for amplitudes std::vector> amplitudes(bitstrings.size(), 0); - Factory factory(param.num_threads); + Factory factory(param.num_threads, 0, 0); if (Runner::Run(param, factory, circuit, parts, bitstrings, amplitudes)) { return amplitudes; From 4918948faa35f33d452c83492e617fb2656af8d1 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 13 Aug 2021 13:30:21 -0700 Subject: [PATCH 043/246] Learning to make --- pybind_interface/Makefile | 25 ++++++++++++++++++++----- qsimcirq_tests/qsimcirq_test.py | 5 +++-- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/pybind_interface/Makefile b/pybind_interface/Makefile index ce2b0b5d..283d4ab9 100644 --- a/pybind_interface/Makefile +++ b/pybind_interface/Makefile @@ -16,17 +16,32 @@ PYBINDFLAGS_AVX512 = -mavx512f -Wall -shared -std=c++17 -fPIC `python3 -m pybind # The flags for the compilation of GPU-specific Pybind11 interfaces PYBINDFLAGS_CUDA = -std=c++17 -x cu -Xcompiler "-Wall -shared -fPIC `python3 -m pybind11 --includes`" -.PHONY: pybind -pybind: +# Check for nvcc to decide compilation mode. +ifeq ($(shell which $(NVCC)),) +pybind: pybind-cpu decide-cpu +else +pybind: pybind-cpu pybind-gpu decide-gpu +endif + +.PHONY: pybind-cpu +pybind-cpu: $(CXX) basic/pybind_main_basic.cpp -o $(QSIMLIB_BASIC) $(CXXFLAGS) $(PYBINDFLAGS_BASIC) $(CXX) sse/pybind_main_sse.cpp -o $(QSIMLIB_SSE) $(CXXFLAGS) $(PYBINDFLAGS_SSE) $(CXX) avx2/pybind_main_avx2.cpp -o $(QSIMLIB_AVX2) $(CXXFLAGS) $(PYBINDFLAGS_AVX2) $(CXX) avx512/pybind_main_avx512.cpp -o $(QSIMLIB_AVX512) $(CXXFLAGS) $(PYBINDFLAGS_AVX512) - if [ -n "$(which $NVCC)" ] ; then\ - $(NVCC) cuda/pybind_main_cuda.cpp -o $(QSIMLIB_CUDA) $(NVCCFLAGS) $(PYBINDFLAGS_CUDA);\ - fi + +.PHONY: decide-cpu +decide-cpu: $(CXX) decide/decide.cpp -o $(QSIMLIB_DECIDE) $(CXXFLAGS) $(PYBINDFLAGS_BASIC) +.PHONY: pybind-gpu +pybind-gpu: + $(NVCC) cuda/pybind_main_cuda.cpp -o $(QSIMLIB_CUDA) $(NVCCFLAGS) $(PYBINDFLAGS_CUDA) + +.PHONY: decide-gpu +decide-gpu: + $(NVCC) decide/decide.cpp -o $(QSIMLIB_DECIDE) $(CXXFLAGS) $(PYBINDFLAGS_BASIC) + .PHONY: clean clean: -rm -f ./basic/*.x ./basic/*.a ./basic/*.so ./basic/*.mod $(QSIMLIB_BASIC) diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index a1fdfe49..0415a40a 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -1069,11 +1069,12 @@ def test_cirq_qsimh_simulate(): def test_qsim_gpu_unavailable(): if qsimcirq.qsim_gpu is not None: pytest.skip("GPU is available; skipping test.") - + # Attempt to create a simulator with GPU support. gpu_options = qsimcirq.QSimOptions(use_gpu=True) with pytest.raises( - ValueError, match="GPU execution requested, but not supported", + ValueError, + match="GPU execution requested, but not supported", ): _ = qsimcirq.QSimSimulator(qsim_options=gpu_options) From ef784c3108b0699ca5402ae745377fdf3a36980d Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 13 Aug 2021 13:48:10 -0700 Subject: [PATCH 044/246] Flag fixes. --- pybind_interface/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybind_interface/Makefile b/pybind_interface/Makefile index 283d4ab9..e3663d90 100644 --- a/pybind_interface/Makefile +++ b/pybind_interface/Makefile @@ -40,7 +40,7 @@ pybind-gpu: .PHONY: decide-gpu decide-gpu: - $(NVCC) decide/decide.cpp -o $(QSIMLIB_DECIDE) $(CXXFLAGS) $(PYBINDFLAGS_BASIC) + $(NVCC) decide/decide.cpp -o $(QSIMLIB_DECIDE) $(NVCCFLAGS) $(PYBINDFLAGS_CUDA) .PHONY: clean clean: From 7d3fee8ee6ef96f3ca8492385f457615d0fc37d1 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 13 Aug 2021 13:54:44 -0700 Subject: [PATCH 045/246] Test old options compatibility --- qsimcirq_tests/qsimcirq_test.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 0415a40a..41873c67 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -1139,6 +1139,20 @@ def test_cirq_qsim_gpu_expectation_values(): assert np.allclose(result, cirq_result) +def test_cirq_qsim_old_options(): + old_options = {"f": 3, "t": 4, "r": 100, "v": 1} + old_sim = qsimcirq.QSimSimulator(qsim_options=old_options) + + new_options = qsimcirq.QSimOptions( + max_fused_gate_size=3, + cpu_threads=4, + ev_noisy_repetitions=100, + verbosity=1, + ) + new_sim = qsimcirq.QSimSimulator(qsim_options=new_options) + assert new_sim.qsim_options == old_sim.qsim_options + + def test_cirq_qsim_params(): qubit = cirq.GridQubit(0, 0) From d2bdfd9a36b3b91dcf4ee8edfccfc29202ac71ef Mon Sep 17 00:00:00 2001 From: Laurynas Tamulevicius Date: Sat, 14 Aug 2021 13:55:25 +0100 Subject: [PATCH 046/246] Fix windows release flow by omitting build for cp35 --- .github/workflows/release_wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_wheels.yml b/.github/workflows/release_wheels.yml index c92a617d..1550d20e 100644 --- a/.github/workflows/release_wheels.yml +++ b/.github/workflows/release_wheels.yml @@ -31,7 +31,7 @@ jobs: name: win_amd64 architecture: x64 cibw: - build: "cp*win_amd64" + build: "cp36-win_amd64 cp37-win_amd64 cp38-win_amd64 cp39-win_amd64" env: CIBW_BUILD: "${{ matrix.cibw.build || '*' }}" CIBW_ARCHS: "${{ matrix.cibw.arch || 'auto' }}" From 0cdfa9b60b6f02abe5a45a13a7057ff4642e4939 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Mon, 16 Aug 2021 14:10:22 +0200 Subject: [PATCH 047/246] Update fuser. --- lib/fuser_mqubit.h | 41 ++++++++++++++---- tests/fuser_mqubit_test.cc | 89 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 8 deletions(-) diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index 66273893..8db3ceae 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -393,17 +393,42 @@ class MultiQubitGateFuser final : public Fuser { FuseGateSequences( max_fused_size, num_qubits, scratch, gates_seq, stat, fused_gates); } else { + unsigned prev_time = 0; + + std::vector orphaned_gates; + orphaned_gates.reserve(num_qubits); + for (auto& fgate : gates_seq) { - if (fgate.gates.size() > 0) { - // Assume fgate.qubits (gate.qubits) are sorted. - fused_gates.push_back({fgate.parent->kind, fgate.parent->time, - std::move(fgate.qubits), fgate.parent, - std::move(fgate.gates)}); - - if (fgate.visited != kMeaCnt) { - ++stat.num_fused_gates; + if (fgate.gates.size() == 0) continue; + + if (prev_time != fgate.parent->time) { + if (orphaned_gates.size() > 0) { + FuseOrphanedGates( + max_fused_size, stat, orphaned_gates, fused_gates); + orphaned_gates.resize(0); } + + prev_time = fgate.parent->time; } + + if (fgate.qubits.size() == 1 && max_fused_size > 1 + && fgate.visited != kMeaCnt && !fgate.parent->unfusible) { + orphaned_gates.push_back(&fgate); + continue; + } + + // Assume fgate.qubits (gate.qubits) are sorted. + fused_gates.push_back({fgate.parent->kind, fgate.parent->time, + std::move(fgate.qubits), fgate.parent, + std::move(fgate.gates)}); + + if (fgate.visited != kMeaCnt) { + ++stat.num_fused_gates; + } + } + + if (orphaned_gates.size() > 0) { + FuseOrphanedGates(max_fused_size, stat, orphaned_gates, fused_gates); } } } diff --git a/tests/fuser_mqubit_test.cc b/tests/fuser_mqubit_test.cc index d63e20af..761b1654 100644 --- a/tests/fuser_mqubit_test.cc +++ b/tests/fuser_mqubit_test.cc @@ -980,6 +980,95 @@ TEST(FuserMultiQubitTest, InvalidTimeOrder) { } } +TEST(FuserMultiQubitTest, OrphanedGates) { + using Fuser = MultiQubitGateFuser; + + std::vector circuit; + circuit.reserve(6); + + Fuser::Parameter param; + param.verbosity = 0; + + for (unsigned num_qubits = 2; num_qubits <= 6; ++ num_qubits) { + circuit.resize(0); + + for (unsigned q = 0; q < num_qubits; ++q) { + circuit.push_back(CreateDummyGate(0, {q})); + } + + for (unsigned f = 2; f <= num_qubits; ++f) { + param.max_fused_size = f; + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end()); + + EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); + EXPECT_EQ(fused_gates.size(), (num_qubits - 1) / f + 1); + } + } + + { + unsigned num_qubits = 4; + std::vector circuit = { + CreateDummyGate(0, {0}), + CreateDummyGate(0, {1}), + CreateDummyGate(0, {2}), + CreateDummyGate(0, {3}), + CreateDummyGate(1, {0, 3}), + }; + + param.max_fused_size = 2; + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end()); + + EXPECT_EQ(fused_gates.size(), 2); + } + + { + unsigned num_qubits = 4; + std::vector circuit = { + CreateDummyGate(0, {0, 3}), + CreateDummyGate(1, {0}), + CreateDummyGate(1, {1}), + CreateDummyGate(1, {2}), + CreateDummyGate(1, {3}), + }; + + param.max_fused_size = 2; + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end()); + + EXPECT_EQ(fused_gates.size(), 2); + } + + { + unsigned num_qubits = 3; + std::vector circuit = { + CreateDummyGate(0, {0}), + CreateDummyControlledGate(0, {1}, {2}), + }; + + param.max_fused_size = 2; + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end()); + + EXPECT_EQ(fused_gates.size(), 2); + } + + { + unsigned num_qubits = 3; + std::vector circuit = { + CreateDummyGate(0, {0}), + CreateDummyMeasurementGate(0, {2}), + }; + + param.max_fused_size = 2; + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end()); + + EXPECT_EQ(fused_gates.size(), 2); + } +} + } // namespace qsim int main(int argc, char** argv) { From e828bd876685b4f6bc3ee7224507f6e4f1dc9fe2 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 16 Aug 2021 07:09:46 -0700 Subject: [PATCH 048/246] Update ubuntu version in all GHA workflows --- .github/workflows/cirq_compatibility.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cirq_compatibility.yml b/.github/workflows/cirq_compatibility.yml index f9a9df71..04e13559 100644 --- a/.github/workflows/cirq_compatibility.yml +++ b/.github/workflows/cirq_compatibility.yml @@ -7,7 +7,7 @@ on: jobs: consistency: name: Nightly Compatibility - runs-on: ubuntu-16.04 + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v1 From 5f7029de1fe9d77b3eb7aa623f4f176c58fb4498 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 16 Aug 2021 07:55:07 -0700 Subject: [PATCH 049/246] Use same bazel version as TFQ. Kokoro tests stil require update. --- .github/workflows/bazeltest.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/bazeltest.yml b/.github/workflows/bazeltest.yml index 9b29cda3..a7fb03f1 100644 --- a/.github/workflows/bazeltest.yml +++ b/.github/workflows/bazeltest.yml @@ -26,8 +26,8 @@ jobs: run: git submodule update --init --recursive - name: Install Bazel on CI run: | - wget https://github.com/bazelbuild/bazel/releases/download/0.26.0/bazel_0.26.0-linux-x86_64.deb - sudo dpkg -i bazel_0.26.0-linux-x86_64.deb + wget https://github.com/bazelbuild/bazel/releases/download/3.7.2/bazel_3.7.2-linux-x86_64.deb + sudo dpkg -i bazel_3.7.2-linux-x86_64.deb - name: Run C++ tests run: | bazel test --config=${{ matrix.hardware_opt }} \ @@ -52,8 +52,8 @@ jobs: run: git submodule update --init --recursive - name: Install Bazel on CI run: | - wget https://github.com/bazelbuild/bazel/releases/download/0.26.0/bazel_0.26.0-linux-x86_64.deb - sudo dpkg -i bazel_0.26.0-linux-x86_64.deb + wget https://github.com/bazelbuild/bazel/releases/download/3.7.2/bazel_3.7.2-linux-x86_64.deb + sudo dpkg -i bazel_3.7.2-linux-x86_64.deb - name: Run C++ tests run: | bazel test --config=avx --config=openmp \ @@ -69,8 +69,8 @@ jobs: run: git submodule update --init --recursive - name: Install Bazel on CI run: | - wget https://github.com/bazelbuild/bazel/releases/download/0.26.0/bazel_0.26.0-linux-x86_64.deb - sudo dpkg -i bazel_0.26.0-linux-x86_64.deb + wget https://github.com/bazelbuild/bazel/releases/download/3.7.2/bazel_3.7.2-linux-x86_64.deb + sudo dpkg -i bazel_3.7.2-linux-x86_64.deb - name: Install google-perftools for tcmalloc run: sudo apt-get install libgoogle-perftools-dev - name: Run C++ tests From e68da688c292dfe4269d1661a29d1db9ecaa0da5 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 16 Aug 2021 08:00:10 -0700 Subject: [PATCH 050/246] Allow AVX512 + sanitizer, part 1/4 --- tests/simulator_avx512_test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/simulator_avx512_test.cc b/tests/simulator_avx512_test.cc index 9265038a..fbc9510e 100644 --- a/tests/simulator_avx512_test.cc +++ b/tests/simulator_avx512_test.cc @@ -16,7 +16,7 @@ #include "gtest/gtest.h" -#if defined(__AVX512F__) && !defined(_WIN32) && !defined(__SANITIZE_ADDRESS__) +#if defined(__AVX512F__) && !defined(_WIN32) #ifdef _OPENMP #include "../lib/parfor.h" @@ -94,7 +94,7 @@ TYPED_TEST(SimulatorAVX512Test, ExpectationValue2) { } // namespace qsim -#endif // defined(__AVX512F__) && !defined(_WIN32) && !defined(__SANITIZE_ADDRESS__) +#endif // defined(__AVX512F__) && !defined(_WIN32) int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); From a85b34e9ab7f2855d7fb86cec6d1f9c4ac4bcd16 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 16 Aug 2021 08:00:28 -0700 Subject: [PATCH 051/246] Allow AVX512 + sanitizer, part 2/4 --- tests/statespace_avx512_test.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/statespace_avx512_test.cc b/tests/statespace_avx512_test.cc index d018eb79..6bbb1f58 100644 --- a/tests/statespace_avx512_test.cc +++ b/tests/statespace_avx512_test.cc @@ -16,7 +16,7 @@ #include "gtest/gtest.h" -#if defined(__AVX512F__) && !defined(_WIN32) && !defined(__SANITIZE_ADDRESS__) +#if defined(__AVX512F__) && !defined(_WIN32) #ifdef _OPENMP #include "../lib/parfor.h" @@ -115,7 +115,7 @@ TYPED_TEST(StateSpaceAVX512Test, ThreadThrashing) { } // namespace qsim -#endif // defined(__AVX512F__) && !defined(_WIN32) && !defined(__SANITIZE_ADDRESS__) +#endif // defined(__AVX512F__) && !defined(_WIN32) int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); From 6bfd7483fe06a18f2d1d8a92caf4695c4c34797e Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 16 Aug 2021 08:00:57 -0700 Subject: [PATCH 052/246] Allow AVX512 + sanitizer, part 3/4 --- tests/unitary_calculator_avx512_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unitary_calculator_avx512_test.cc b/tests/unitary_calculator_avx512_test.cc index e7cad957..56982688 100644 --- a/tests/unitary_calculator_avx512_test.cc +++ b/tests/unitary_calculator_avx512_test.cc @@ -16,7 +16,7 @@ #include "gtest/gtest.h" -#if defined(__AVX512F__) && !defined(_WIN32) && !defined(__SANITIZE_ADDRESS__) +#if defined(__AVX512F__) && !defined(_WIN32) #include "../lib/formux.h" #include "../lib/unitary_calculator_avx512.h" From fc4ce0dada1e8833135f8aff23e1e20a6632ce3b Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 16 Aug 2021 08:01:22 -0700 Subject: [PATCH 053/246] Allow AVX512 + sanitizer, part 4/4 --- tests/unitaryspace_avx512_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unitaryspace_avx512_test.cc b/tests/unitaryspace_avx512_test.cc index 21f2b0be..6301ec4d 100644 --- a/tests/unitaryspace_avx512_test.cc +++ b/tests/unitaryspace_avx512_test.cc @@ -16,7 +16,7 @@ #include "gtest/gtest.h" -#if defined(__AVX512F__) && !defined(_WIN32) && !defined(__SANITIZE_ADDRESS__) +#if defined(__AVX512F__) && !defined(_WIN32) #include "../lib/formux.h" #include "../lib/unitaryspace_avx512.h" From 79d8c0ca94897a8d14791a42c8419739dfe0b256 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 16 Aug 2021 09:39:21 -0700 Subject: [PATCH 054/246] Update version to 0.10.2 --- qsimcirq/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq/_version.py b/qsimcirq/_version.py index 30919655..d9e1c77d 100644 --- a/qsimcirq/_version.py +++ b/qsimcirq/_version.py @@ -1,3 +1,3 @@ """The version number defined here is read automatically in setup.py.""" -__version__ = "0.10.1" +__version__ = "0.10.2" From 089f070e5b12ff1bc1b5612a30c758f3ba71ed32 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 16 Aug 2021 10:36:09 -0700 Subject: [PATCH 055/246] Save time in Docker tests --- pybind_interface/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybind_interface/Dockerfile b/pybind_interface/Dockerfile index c74fbacc..994bc2a5 100644 --- a/pybind_interface/Dockerfile +++ b/pybind_interface/Dockerfile @@ -6,7 +6,7 @@ RUN apt-get install -y python3-dev python3-pybind11 python3-pytest python3-pip # The --force flag is used mainly so that the old numpy installation from pybind # gets replaced with the one cirq requires -RUN pip3 install cirq --force +RUN pip3 install cirq-core --force # Copy relevant files COPY ./pybind_interface/ /qsim/pybind_interface/ From 50b80e2e8a59bfce8d29d17266044bed291da716 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 16 Aug 2021 11:15:39 -0700 Subject: [PATCH 056/246] prefer-binary --- pybind_interface/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybind_interface/Dockerfile b/pybind_interface/Dockerfile index 994bc2a5..b826d197 100644 --- a/pybind_interface/Dockerfile +++ b/pybind_interface/Dockerfile @@ -6,7 +6,7 @@ RUN apt-get install -y python3-dev python3-pybind11 python3-pytest python3-pip # The --force flag is used mainly so that the old numpy installation from pybind # gets replaced with the one cirq requires -RUN pip3 install cirq-core --force +RUN pip3 install --prefer-binary cirq-core --force # Copy relevant files COPY ./pybind_interface/ /qsim/pybind_interface/ From 9ba2c1019423634bd0a6f00508886a0184fc5355 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 17 Aug 2021 11:17:16 -0700 Subject: [PATCH 057/246] Review comments --- pybind_interface/Makefile | 2 +- pybind_interface/decide/decide.cpp | 2 +- pybind_interface/pybind_main.cpp | 35 ++++++++++++------------------ qsimcirq/qsim_simulator.py | 12 +++++----- 4 files changed, 22 insertions(+), 29 deletions(-) diff --git a/pybind_interface/Makefile b/pybind_interface/Makefile index e3663d90..daebea5e 100644 --- a/pybind_interface/Makefile +++ b/pybind_interface/Makefile @@ -14,7 +14,7 @@ PYBINDFLAGS_AVX2 = -mavx2 -mfma -Wall -shared -std=c++17 -fPIC `python3 -m pybin PYBINDFLAGS_AVX512 = -mavx512f -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` # The flags for the compilation of GPU-specific Pybind11 interfaces -PYBINDFLAGS_CUDA = -std=c++17 -x cu -Xcompiler "-Wall -shared -fPIC `python3 -m pybind11 --includes`" +PYBINDFLAGS_CUDA = -std=c++14 -x cu -Xcompiler "-Wall -shared -fPIC `python3 -m pybind11 --includes`" # Check for nvcc to decide compilation mode. ifeq ($(shell which $(NVCC)),) diff --git a/pybind_interface/decide/decide.cpp b/pybind_interface/decide/decide.cpp index 937c24ac..890f7a41 100644 --- a/pybind_interface/decide/decide.cpp +++ b/pybind_interface/decide/decide.cpp @@ -33,7 +33,7 @@ int detect_instructions() { } if (nIds >= 7) { cpuid(info, 7); - if ((info[1] & (1 << 5) )!= 0) { + if ((info[1] & (1 << 5))!= 0) { instr = AVX2; } if ((info[1] & (1 << 16)) != 0) { diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 1d3b951f..16da8aec 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -458,10 +458,9 @@ std::vector> qtrajectory_simulate(const py::dict &options) { return {}; } - Simulator simulator = Factory( - num_sim_threads, num_state_threads, num_dblocks).CreateSimulator(); - StateSpace state_space = Factory( - num_sim_threads, num_state_threads, num_dblocks).CreateStateSpace(); + Factory factory(num_sim_threads, num_state_threads, num_dblocks); + Simulator simulator = factory.CreateSimulator(); + StateSpace state_space = factory.CreateStateSpace(); auto measure = [&bitstrings, &ncircuit, &litudes, &state_space]( unsigned k, const State &state, @@ -613,22 +612,19 @@ class SimulatorHelper { bool simulate(const StateType& input_state) { init_state(input_state); bool result = false; + + Factory factory(num_sim_threads, num_state_threads, num_dblocks); if (is_noisy) { std::vector stat; auto params = get_noisy_params(); - Simulator simulator = Factory( - num_sim_threads, num_state_threads, num_dblocks).CreateSimulator(); - StateSpace state_space = Factory( - num_sim_threads, num_state_threads, num_dblocks).CreateStateSpace(); + Simulator simulator = factory.CreateSimulator(); + StateSpace state_space = factory.CreateStateSpace(); result = NoisyRunner::RunOnce(params, ncircuit, seed, state_space, simulator, scratch, state, stat); } else { - result = Runner::Run( - get_params(), - Factory(num_sim_threads, num_state_threads, num_dblocks), - circuit, state); + result = Runner::Run(get_params(), factory, circuit, state); } seed += 1; return result; @@ -803,14 +799,12 @@ std::vector qsim_sample(const py::dict &options) { } std::vector results; - StateSpace state_space = Factory( - num_sim_threads, num_state_threads, num_dblocks).CreateStateSpace(); + Factory factory(num_sim_threads, num_state_threads, num_dblocks); + StateSpace state_space = factory.CreateStateSpace(); State state = state_space.Create(circuit.num_qubits); state_space.SetStateZero(state); - if (!Runner::Run( - param, Factory(num_sim_threads, num_state_threads, num_dblocks), - circuit, state, results)) { + if (!Runner::Run(param, factory, circuit, state, results)) { IO::errorf("qsim sampling of the circuit errored out.\n"); return {}; } @@ -864,10 +858,9 @@ std::vector qtrajectory_sample(const py::dict &options) { return {}; } - Simulator simulator = Factory( - num_sim_threads, num_state_threads, num_dblocks).CreateSimulator(); - StateSpace state_space = Factory( - num_sim_threads, num_state_threads, num_dblocks).CreateStateSpace(); + Factory factory(num_sim_threads, num_state_threads, num_dblocks); + Simulator simulator = factory.CreateSimulator(); + StateSpace state_space = factory.CreateStateSpace(); std::vector> results; diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index ee7a593d..f079f55e 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -135,10 +135,10 @@ class QSimOptions: simulation modes. use_gpu: whether to use GPU instead of CPU for simulation. The "gpu_*" arguments below are only considered if this is set to True. - gpu_sim_threads: number of threads to use for the GPU Simulator. - This must be a power of 2 in the range [32, 256]. - gpu_state_threads: number of threads to use for the GPU StateSpace. - This must be a power of 2 in the range [32, 1024]. + gpu_sim_threads: number of threads per CUDA block to use for the GPU + Simulator. This must be a power of 2 in the range [32, 256]. + gpu_state_threads: number of threads per CUDA block to use for the GPU + StateSpace. This must be a power of 2 in the range [32, 1024]. gpu_data_blocks: number of data blocks to use on GPU. Below 16 data blocks, performance is noticeably reduced. verbosity: Logging verbosity. @@ -148,8 +148,8 @@ class QSimOptions: cpu_threads: int = 1 ev_noisy_repetitions: int = 1 use_gpu: bool = False - gpu_sim_threads: int = 32 - gpu_state_threads: int = 32 + gpu_sim_threads: int = 256 + gpu_state_threads: int = 512 gpu_data_blocks: int = 16 verbosity: int = 0 From 5f3c31f2a3e12b1ca264180d09cc55638b099ecd Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 17 Aug 2021 11:54:34 -0700 Subject: [PATCH 058/246] Fix init_state --- pybind_interface/pybind_main.cpp | 7 +------ qsimcirq_tests/qsimcirq_test.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 16da8aec..95c09f77 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -584,12 +584,7 @@ class SimulatorHelper { } void init_state(const py::array_t &input_vector) { - const float* ptr = input_vector.data(); - auto f = [](unsigned n, unsigned m, uint64_t i, const float* ptr, - float* fsv) { - fsv[i] = ptr[i]; - }; - For(num_sim_threads).Run(input_vector.size(), f, ptr, state.get()); + state_space.Copy(input_vector.data(), state); state_space.NormalToInternalOrder(state); } diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 41873c67..e11de449 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -1139,6 +1139,29 @@ def test_cirq_qsim_gpu_expectation_values(): assert np.allclose(result, cirq_result) +def test_cirq_qsim_gpu_input_state(): + if qsimcirq.qsim_gpu is None: + pytest.skip("GPU is not available for testing.") + # Pick qubits. + a, b = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] + + # Create a circuit + cirq_circuit = cirq.Circuit(cirq.H(a), cirq.CNOT(a, b), cirq.X(b)) + + # Enable GPU acceleration. + gpu_options = qsimcirq.QSimOptions(use_gpu=True) + qsimGpuSim = qsimcirq.QSimSimulator(qsim_options=gpu_options) + initial_state = np.asarray([0.5] * 4, dtype=np.complex64) + result = qsimGpuSim.simulate(cirq_circuit, initial_state=initial_state) + assert result.state_vector().shape == (4,) + + cirqSim = cirq.Simulator() + cirq_result = cirqSim.simulate(cirq_circuit, initial_state=initial_state) + assert cirq.linalg.allclose_up_to_global_phase( + result.state_vector(), cirq_result.state_vector(), atol=1.0e-6 + ) + + def test_cirq_qsim_old_options(): old_options = {"f": 3, "t": 4, "r": 100, "v": 1} old_sim = qsimcirq.QSimSimulator(qsim_options=old_options) From 7225b5c052d9af43019a0d7774763317e2b18637 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 17 Aug 2021 13:19:59 -0700 Subject: [PATCH 059/246] GPU doc update. --- docs/cirq_interface.md | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/docs/cirq_interface.md b/docs/cirq_interface.md index 20291fe8..c8058eda 100644 --- a/docs/cirq_interface.md +++ b/docs/cirq_interface.md @@ -76,11 +76,11 @@ This circuit can then be simulated using either `QSimSimulator` or acquiring the complete state of a reasonably-sized circuit (~25 qubits on an average PC, or up to 40 qubits on high-performance VMs). Options for the simulator, including number of threads and verbosity, can be -set with the `qsim_options` field using the `qsim_base` flag format defined in -the [usage docs](./usage.md). +set with the `qsim_options` field, which accepts a `QSimOptions` object as +defined in [qsim_simulator.py](https://github.com/quantumlib/qsim/blob/master/qsimcirq/qsim_simulator.py). ``` -qsim_options = {'t': 8, 'v': 0} +qsim_options = qsimcirq.QSimOptions(cpu_threads=8, verbosity=0) my_sim = qsimcirq.QSimSimulator(qsim_options) myres = my_sim.simulate(program=my_circuit) ``` @@ -136,8 +136,8 @@ outlined in the [usage docs](./usage.md). ## Additional features -The qsim-Cirq interface provides basic support for gate decomposition and -circuit parameterization. +The qsim-Cirq interface supports arbitrary gates and circuit parameterization. +Additionally, GPU execution of circuits can be requested if GPUs are available. ### Gate decompositions @@ -148,7 +148,26 @@ matrices, if one is specified. ### Parametrized circuits -QSimCircuit objects can also contain +`QSimCircuit` objects can also contain [parameterized gates](https://cirq.readthedocs.io/en/stable/docs/tutorials/basics.html#Using-parameter-sweeps) which have values assigned by Cirq's `ParamResolver`. See the link above for details on how to use this feature. + +### GPU execution + +`QSimSimulator` provides optional support for GPU execution of circuits, which +may improve performance. In order to use this feature, qsim must be compiled on +a device with the [CUDA toolkit](https://developer.nvidia.com/cuda-downloads) +and run on a device with available NVIDIA GPUs. + +Compilation for GPU follows the same steps outlined in the +[Compiling qsimcirq](./cirq_interface.md#compiling-qsimcirq) section. + +`QSimOptions` provides four parameters to configure GPU execution: +* use_gpu: whether to use GPU instead of CPU for simulation. +* gpu_sim_threads: number of threads per CUDA block to use for the GPU +Simulator. This must be a power of 2 in the range [32, 256]. +* gpu_state_threads: number of threads per CUDA block to use for the GPU +StateSpace. This must be a power of 2 in the range [32, 1024]. +* gpu_data_blocks: number of data blocks to use on GPU. Below 16 data blocks, +performance is noticeably reduced. From cbb51346bbf67160bb51083d8aefe840398e6b74 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 18 Aug 2021 08:15:45 -0700 Subject: [PATCH 060/246] Clarify options. --- docs/cirq_interface.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/cirq_interface.md b/docs/cirq_interface.md index c8058eda..4f5d190a 100644 --- a/docs/cirq_interface.md +++ b/docs/cirq_interface.md @@ -75,11 +75,16 @@ This circuit can then be simulated using either `QSimSimulator` or `QSimSimulator` uses a Schrödinger full state-vector simulator, suitable for acquiring the complete state of a reasonably-sized circuit (~25 qubits on an average PC, or up to 40 qubits on high-performance VMs). + Options for the simulator, including number of threads and verbosity, can be set with the `qsim_options` field, which accepts a `QSimOptions` object as -defined in [qsim_simulator.py](https://github.com/quantumlib/qsim/blob/master/qsimcirq/qsim_simulator.py). +defined in +[qsim_simulator.py](https://github.com/quantumlib/qsim/blob/master/qsimcirq/qsim_simulator.py). +These options can also be passed as a {str: val} dict, using the format +described by that class. ``` +# equivalent to {'t': 8, 'v': 0} qsim_options = qsimcirq.QSimOptions(cpu_threads=8, verbosity=0) my_sim = qsimcirq.QSimSimulator(qsim_options) myres = my_sim.simulate(program=my_circuit) @@ -163,11 +168,16 @@ and run on a device with available NVIDIA GPUs. Compilation for GPU follows the same steps outlined in the [Compiling qsimcirq](./cirq_interface.md#compiling-qsimcirq) section. -`QSimOptions` provides four parameters to configure GPU execution: -* use_gpu: whether to use GPU instead of CPU for simulation. -* gpu_sim_threads: number of threads per CUDA block to use for the GPU +`QSimOptions` provides four parameters to configure GPU execution. Only +`use_gpu` is required to enable GPU execution: +* `use_gpu`: if True, use GPU instead of CPU for simulation. + +The remaining parameters use default values which provide good performance in +most cases, but can optionally be set to fine-tune perfomance for a specific +device or circuit. +* `gpu_sim_threads`: number of threads per CUDA block to use for the GPU Simulator. This must be a power of 2 in the range [32, 256]. -* gpu_state_threads: number of threads per CUDA block to use for the GPU +* `gpu_state_threads`: number of threads per CUDA block to use for the GPU StateSpace. This must be a power of 2 in the range [32, 1024]. -* gpu_data_blocks: number of data blocks to use on GPU. Below 16 data blocks, +* `gpu_data_blocks`: number of data blocks to use on GPU. Below 16 data blocks, performance is noticeably reduced. From 0e2e650eccde3208f1b39f85dc57eb864420ab81 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Wed, 18 Aug 2021 16:35:20 +0000 Subject: [PATCH 061/246] Added autoscaler.py. MIG autoscaler not working. --- .../terraform/htcondor/autoscaler.py | 36 +-- .../multinode/terraform/htcondor/resources.tf | 15 +- .../terraform/htcondor/startup-centos.sh | 280 +----------------- docs/tutorials/multinode/terraform/init.sh | 29 ++ docs/tutorials/multinode/terraform/main.tf | 1 + 5 files changed, 71 insertions(+), 290 deletions(-) create mode 100644 docs/tutorials/multinode/terraform/init.sh diff --git a/docs/tutorials/multinode/terraform/htcondor/autoscaler.py b/docs/tutorials/multinode/terraform/htcondor/autoscaler.py index 68c7a01d..12036066 100644 --- a/docs/tutorials/multinode/terraform/htcondor/autoscaler.py +++ b/docs/tutorials/multinode/terraform/htcondor/autoscaler.py @@ -29,12 +29,13 @@ import argparse parser = argparse.ArgumentParser() -parser.add_argument("--p", help="Project id", type=str) -parser.add_argument("--r", help="GCP region where the managed instance group is located", type=str) -parser.add_argument("--z", help="Name of GCP zone where the managed instance group is located", type=str) -parser.add_argument("--g", help="Name of the managed instance group", type=str) -parser.add_argument("--c", help="Maximum number of compute instances", type=int) -parser.add_argument("--v", help="Increase output verbosity. 1-show basic debug info. 2-show detail debug info", type=int, choices=[0, 1, 2]) +parser.add_argument("--p", required=True, help="Project id", type=str) +parser.add_argument("--z", required=True, help="Name of GCP zone where the managed instance group is located", type=str) +parser.add_argument("--g", required=True, help="Name of the managed instance group", type=str) +parser.add_argument("--c", required=True, help="Maximum number of compute instances", type=int) +parser.add_argument("--v", default=0, help="Increase output verbosity. 1-show basic debug info. 2-show detail debug info", type=int, choices=[0, 1, 2]) +parser.add_argument("--d", default=0, help="Dry Run, default=0, if 1, then no scaling actions", type=int, choices=[0, 1]) + args = parser.parse_args() @@ -57,12 +58,15 @@ def deleteFromMig(self, instance): self.service.instanceGroupManagers().deleteInstances(project=self.project, zone=self.zone, instanceGroupManager=self.instance_group_manager, body=instances_to_delete) - response = requestDelInstance.execute() - if self.debug > 0: - print('Request to delete instance ' + instance) - pprint(response) - - return response + + # execute if not a dry-run + if not self.dryrun: + response = requestDelInstance.execute() + if self.debug > 0: + print('Request to delete instance ' + instance) + pprint(response) + return response + return "Dry Run" def getInstanceTemplateInfo(self): requestTemplateName = \ @@ -118,7 +122,6 @@ def scale(self): if self.debug > 1: print('Launching autoscaler.py with the following arguments:') print('project_id: ' + self.project) - print('region: ' + self.region) print('zone: ' + self.zone) print('group_manager: ' + self.instance_group_manager) print('computeinstancelimit: ' + str(self.compute_instance_limit)) @@ -258,13 +261,9 @@ def main(): scaler = AutoScaler() - exit() # Project ID scaler.project = args.p # Ex:'slurm-var-demo' - # Region where the managed instance group is located - scaler.region = args.r # Ex: 'us-central1' - # Name of the zone where the managed instance group is located scaler.zone = args.z # Ex: 'us-central1-f' @@ -276,6 +275,9 @@ def main(): # Default number of running instances that the managed instance group should maintain at any given time. This number will go up and down based on the load (number of jobs in the queue) scaler.size = 0 + + # Dry run: : 0, run scaling; 1, only provide info. + scaler.dryrun = args.d > 0 # Debug level: 1-print debug information, 2 - print detail debug information scaler.debug = 0 diff --git a/docs/tutorials/multinode/terraform/htcondor/resources.tf b/docs/tutorials/multinode/terraform/htcondor/resources.tf index 634a5814..42cff48e 100644 --- a/docs/tutorials/multinode/terraform/htcondor/resources.tf +++ b/docs/tutorials/multinode/terraform/htcondor/resources.tf @@ -61,6 +61,7 @@ variable "service_account" { default = "default" } locals{ + autoscaler = file("${path.module}/autoscaler.py") compute_startup = templatefile( "${path.module}/startup-centos.sh", { @@ -68,7 +69,10 @@ locals{ "cluster_name" = var.cluster_name, "htserver_type" = "compute", "osversion" = var.osversion, + "zone" = var.zone, "condorversion" = var.condorversion, + "max_replicas" = var.max_replicas, + "autoscaler" = "", "admin_email" = var.admin_email }) submit_startup = templatefile( @@ -79,6 +83,9 @@ locals{ "htserver_type" = "submit", "osversion" = var.osversion, "condorversion" = var.condorversion, + "zone" = var.zone, + "max_replicas" = var.max_replicas, + "autoscaler" = local.autoscaler, "admin_email" = var.admin_email }) manager_startup = templatefile( @@ -88,7 +95,10 @@ locals{ "cluster_name" = var.cluster_name, "htserver_type" = "manager", "osversion" = var.osversion, + "zone" = var.zone, + "max_replicas" = var.max_replicas, "condorversion" = var.condorversion, + "autoscaler" = "", "admin_email" = var.admin_email }) } @@ -262,7 +272,7 @@ resource "google_compute_instance_group_manager" "condor-compute-igm" { name = var.cluster_name project = var.project - target_size = "1" + target_size = "0" update_policy { max_surge_fixed = 2 @@ -324,3 +334,6 @@ resource "google_compute_autoscaler" "condor-compute-as" { } */ +output "startup_script" { + value = local.submit_startup +} \ No newline at end of file diff --git a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh index 1336b547..71f5d423 100644 --- a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh +++ b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh @@ -169,286 +169,22 @@ fi service google-fluentd restart # Add Python Libraries and Autoscaler + if [ "$SERVER_TYPE" == "submit" ]; then python3 -m pip install --upgrade oauth2client python3 -m pip install --upgrade google-api-python-client python3 -m pip install --upgrade absl-py -fi cat < /opt/autoscaler.py -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright 2018 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Script for resizing managed instance group (MIG) cluster size based -# on the number of jobs in the Condor Queue. - -from pprint import pprint -from googleapiclient import discovery -from oauth2client.client import GoogleCredentials - -import os -import math -import argparse - -parser = argparse.ArgumentParser("autoscaler.py") -parser.add_argument("-p", "--project_id", help="Project id", type=str) -parser.add_argument("-r", "--region", help="GCP region where the managed instance group is located", type=str) -parser.add_argument("-z", "--zone", help="Name of GCP zone where the managed instance group is located", type=str) -parser.add_argument("-g", "--group_manager", help="Name of the managed instance group", type=str) -parser.add_argument("-c", "--computeinstancelimit", help="Maximum number of compute instances", type=int) -parser.add_argument("-v", "--verbosity", help="Increase output verbosity. 1-show basic debug info. 2-show detail debug info", type=int, choices=[0, 1, 2]) -args = parser.parse_args() - -# Project ID -project = args.project_id # Ex:'slurm-var-demo' - -# Region where the managed instance group is located -region = args.region # Ex: 'us-central1' - -# Name of the zone where the managed instance group is located -zone = args.zone # Ex: 'us-central1-f' - -# The name of the managed instance group. -instance_group_manager = args.group_manager # Ex: 'condor-compute-igm' - -# Default number of cores per intance, will be replaced with actual value -cores_per_node = 4 - -# Default number of running instances that the managed instance group should maintain at any given time. This number will go up and down based on the load (number of jobs in the queue) -size = 0 - -# Debug level: 1-print debug information, 2 - print detail debug information -debug = 0 -if (args.verbosity): - debug = args.verbosity - -# Limit for the maximum number of compute instance. If zero (default setting), no limit will be enforced by the script -compute_instance_limit = 0 -if (args.computeinstancelimit): - compute_instance_limit = abs(args.computeinstancelimit) - - -if debug > 1: - print('Launching autoscaler.py with the following arguments:') - print('project_id: ' + project) - print('region: ' + region) - print('zone: ' + zone) - print('group_manager: ' + instance_group_manager) - print('computeinstancelimit: ' + str(compute_instance_limit)) - print('debuglevel: ' + str(debug)) - - -# Remove specified instance from MIG and decrease MIG size -def deleteFromMig(instance): - instanceUrl = 'https://www.googleapis.com/compute/v1/projects/' \ - + project + '/zones/' + zone + '/instances/' + instance - instances_to_delete = {'instances': [instanceUrl]} - - requestDelInstance = \ - service.instanceGroupManagers().deleteInstances(project=project, - zone=zone, instanceGroupManager=instance_group_manager, - body=instances_to_delete) - response = requestDelInstance.execute() - if debug > 0: - print('Request to delete instance ' + instance) - pprint(response) - - return response - -def getInstanceTemplateInfo(): - requestTemplateName = \ - service.instanceGroupManagers().get(project=project, zone=zone, - instanceGroupManager=instance_group_manager, - fields='instanceTemplate') - responseTemplateName = requestTemplateName.execute() - template_name = '' - - if debug > 1: - print('Request for the template name') - pprint(responseTemplateName) - - if len(responseTemplateName) > 0: - template_url = responseTemplateName.get('instanceTemplate') - template_url_partitioned = template_url.split('/') - template_name = \ - template_url_partitioned[len(template_url_partitioned) - 1] - - requestInstanceTemplate = \ - service.instanceTemplates().get(project=project, - instanceTemplate=template_name, fields='properties') - responseInstanceTemplateInfo = requestInstanceTemplate.execute() - - if debug > 1: - print('Template information') - pprint(responseInstanceTemplateInfo['properties']) - - machine_type = responseInstanceTemplateInfo['properties']['machineType'] - is_preemtible = responseInstanceTemplateInfo['properties']['scheduling']['preemptible'] - if debug > 0: - print('Machine Type: ' + machine_type) - print('Is preemtible: ' + str(is_preemtible)) - request = service.machineTypes().get(project=project, zone=zone, - machineType=machine_type) - response = request.execute() - guest_cpus = response['guestCpus'] - if debug > 1: - print('Machine information') - pprint(responseInstanceTemplateInfo['properties']) - if debug > 0: - print('Guest CPUs: ' + str(guest_cpus)) - - instanceTemlateInfo = {'machine_type': machine_type, - 'is_preemtible': is_preemtible, - 'guest_cpus': guest_cpus} - return instanceTemlateInfo - - -# Obtain credentials -credentials = GoogleCredentials.get_application_default() -service = discovery.build('compute', 'v1', credentials=credentials) - -# Get total number of jobs in the queue that includes number of jos waiting as well as number of jobs already assigned to nodes -queue_length_req = 'condor_q -totals -format "%d " Jobs -format "%d " Idle -format "%d " Held' -queue_length_resp = os.popen(queue_length_req).read().split() - -if len(queue_length_resp) > 1: - queue = int(queue_length_resp[0]) - idle_jobs = int(queue_length_resp[1]) - on_hold_jobs = int(queue_length_resp[2]) -else: - queue = 0 - idle_jobs = 0 - on_hold_jobs = 0 - -print('Total queue length: ' + str(queue)) -print('Idle jobs: ' + str(idle_jobs)) -print('Jobs on hold: ' + str(on_hold_jobs)) - -instanceTemlateInfo = getInstanceTemplateInfo() -if debug > 1: - print('Information about the compute instance template') - pprint(instanceTemlateInfo) - -cores_per_node = instanceTemlateInfo['guest_cpus'] -print('Number of CPU per compute node: ' + str(cores_per_node)) - -# Get state for for all jobs in Condor -name_req = 'condor_status -af name state' -slot_names = os.popen(name_req).read().splitlines() -if debug > 1: - print('Currently running jobs in Condor') - print(slot_names) - -# Adjust current queue length by the number of jos that are on-hold -queue -=on_hold_jobs -if on_hold_jobs>0: - print("Adjusted queue length: " + str(queue)) - -# Calculate number instances to satisfy current job queue length -if queue > 0: - size = int(math.ceil(float(queue) / float(cores_per_node))) - if debug>0: - print("Calucalting size of MIG: ⌈" + str(queue) + "/" + str(cores_per_node) + "⌉ = " + str(size)) -else: - size = 0 - -# If compute instance limit is specified, can not start more instances then specified in the limit -if compute_instance_limit > 0 and size > compute_instance_limit: - size = compute_instance_limit; - print("MIG target size will be limited by " + str(compute_instance_limit)) - -print('New MIG target size: ' + str(size)) - -# Get current number of instances in the MIG -requestGroupInfo = service.instanceGroupManagers().get(project=project, - zone=zone, instanceGroupManager=instance_group_manager) -responseGroupInfo = requestGroupInfo.execute() -currentTarget = int(responseGroupInfo['targetSize']) -print('Current MIG target size: ' + str(currentTarget)) - -if debug > 1: - print('MIG Information:') - print(responseGroupInfo) - -if size == 0 and currentTarget == 0: - print('No jobs in the queue and no compute instances running. Nothing to do') - exit() - -if size == currentTarget: - print('Running correct number of compute nodes to handle number of jobs in the queue') - exit() - - -if size < currentTarget: - print('Scaling down. Looking for nodes that can be shut down' ) - # Find nodes that are not busy (all slots showing status as "Unclaimed") +${autoscaler} +EOFZ - node_busy = {} - for slot_name in slot_names: - name_status = slot_name.split() - if len(name_status) > 1: - name = name_status[0] - status = name_status[1] - slot = "NO-SLOT" - slot_server = name.split('@') - if len(slot_server) > 1: - slot = slot_server[0] - server = slot_server[1].split('.')[0] - else: - server = slot_server[0].split('.')[0] +# Create cron entry for autoscaler. Log to /var/log/messages - if debug > 0: - print(slot + ', ' + server + ', ' + status + '\n') +echo "* * * * * python3 /opt/autoscaler.py --p ${project} --z ${zone} --g ${cluster_name} --c ${max_replicas} | logger " |crontab - - if server not in node_busy: - if status == 'Unclaimed': - node_busy[server] = False - else: - node_busy[server] = True - else: - if status != 'Unclaimed': - node_busy[server] = True - - if debug > 1: - print('Compuute node busy status:') - print(node_busy) +fi - # Shut down nodes that are not busy - for node in node_busy: - if not node_busy[node]: - print('Will shut down: ' + node + ' ...') - respDel = deleteFromMig(node) - if debug > 1: - print("Shut down request for compute node " + node) - pprint(respDel) - - if debug > 1: - print("Scaling down complete") +# Now we can let everyone know that the setup is complete. -if size > currentTarget: - print("Scaling up. Need to increase number of instances to " + str(size)) - #Request to resize - request = service.instanceGroupManagers().resize(project=project, - zone=zone, - instanceGroupManager=instance_group_manager, - size=size) - response = request.execute() - if debug > 1: - print('Requesting to increase MIG size') - pprint(response) - print("Scaling up complete") -EOFZ +wall "******* HTCondor system configuration complete ********" \ No newline at end of file diff --git a/docs/tutorials/multinode/terraform/init.sh b/docs/tutorials/multinode/terraform/init.sh new file mode 100644 index 00000000..82461e55 --- /dev/null +++ b/docs/tutorials/multinode/terraform/init.sh @@ -0,0 +1,29 @@ +export TF_VAR_project=quantum-htcondor-13 +export TF_VAR_project_id=us-east4-c +export TF_VAR_zone=us-east4-c +export TF_VAR_region=us-east4 +export TF_VAR_project_id=${TF_VAR_project} +export TF_VAR_service_account="htcondor@"${TF_VAR_project}".iam.gserviceaccount.com" + + +gcloud config set project $TF_VAR_project +gcloud services enable compute.googleapis.com +gcloud services enable monitoring.googleapis.com +gcloud config set compute/zone $TF_VAR_zone +gcloud config set compute/region $TF_VAR_region + +gcloud config list + + +gcloud iam service-accounts create htcondor --display-name="Run HTCondor" + +# Add roles +gcloud projects add-iam-policy-binding ${TF_VAR_project} --member serviceAccount:${TF_VAR_service_account} --role roles/compute.admin +gcloud projects add-iam-policy-binding ${TF_VAR_project} --member serviceAccount:${TF_VAR_service_account} --role roles/iam.serviceAccountUser +gcloud projects add-iam-policy-binding ${TF_VAR_project} --member serviceAccount:${TF_VAR_service_account} --role roles/monitoring.admin +gcloud projects add-iam-policy-binding ${TF_VAR_project} --member serviceAccount:${TF_VAR_service_account} --role roles/logging.admin +gcloud projects add-iam-policy-binding ${TF_VAR_project} --member serviceAccount:${TF_VAR_service_account} --role roles/autoscaling.metricsWriter + +# Save key file locally. +gcloud iam service-accounts keys create ~/.${TF_VAR_project_id}.json --iam-account=${TF_VAR_service_account} +export GOOGLE_APPLICATION_CREDENTIALS=~/.${TF_VAR_project_id}.json \ No newline at end of file diff --git a/docs/tutorials/multinode/terraform/main.tf b/docs/tutorials/multinode/terraform/main.tf index c883028c..0c0a8f87 100644 --- a/docs/tutorials/multinode/terraform/main.tf +++ b/docs/tutorials/multinode/terraform/main.tf @@ -19,6 +19,7 @@ module "htcondor" { osversion = "7" max_replicas=20 min_replicas=0 + compute_instance_type = "custom-2-11264" service_account="htcondor@${var.project}.iam.gserviceaccount.com" use_preemptibles=false osproject ="centos-cloud" From 98e9771df5e1045eed87745b54b620fa9d6da4bc Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Wed, 18 Aug 2021 18:57:23 +0000 Subject: [PATCH 062/246] updated readme --- docs/tutorials/multinode/terraform/README.md | 161 +++++++++++++++---- 1 file changed, 131 insertions(+), 30 deletions(-) diff --git a/docs/tutorials/multinode/terraform/README.md b/docs/tutorials/multinode/terraform/README.md index 3669fcfd..b4464ddf 100644 --- a/docs/tutorials/multinode/terraform/README.md +++ b/docs/tutorials/multinode/terraform/README.md @@ -1,49 +1,150 @@ -# HTCondor on GCP +# Multinode quantum simulation using HTCondor on GCP +This tutorial will take you through the process of running many qsim simulations +on Google Cloud. In some situations, it is required to run many instances of the same +simulation. This could be used to provide a parameter sweep or to evaluation noise characteristics. +## Create a project +In a seperate tutorial the method to create Google Cloud project is explained in detail. +[Please refer to this tutorial for guidance](https://quantumai.google/qsim/tutorials/qsimcirq_gcp#gcp_setup). + +When you have a project created, move on to the next step. + +## Configure your enviroment + +In your procject using the Cloud Console(https://console.google.com/home/dashboard?cloudshell=true), clone this Github repo. ``` -gcloud config set project [[PROJECT_ID]] -gcloud config set compute/region [[YOUR_REGION]] -gcloud config set compute/zone [[YOUR_ZONE]] +git clone https://github.com/jrossthomson/qsim.git ``` +Change directory to the tutorial: ``` -git clone https://github.com/lsst-dm/google-poc-2020.git -cd google-poc-2020 +cd qsim/docs/tutorials/multinode/terraform ``` +This is where you will use terraform to create the HTCondor cluster required to run your jobs. +### Edit init file to setup environment + +You can now edit `init.sh` to change the name of the project you are using. You can also change the zone and region as you see fit. More information is available [here](https://cloud.google.com/compute/docs/regions-zones). + +Change the variables to reflect your project, most important is the first line: ``` -gcloud iam service-accounts create htcondor +export TF_VAR_project=my-quantum-htcondor-project ``` +The project id needs to be globally unique and to be the same as the project you just created. + +The edited `init.sh` file should be "sourced" in the cloud shell: ``` -gcloud projects add-iam-policy-binding \ - --member serviceAccount:htcondor@[[PROJECT]] \ - --role roles/iam.serviceAccountUser +source init.sh ``` +Repsond in the affirmative to any pop-ups that request permissions on the Cloud platform. +The final outcome of this script will include: -## Updating the main.tf Terraform file +* A gcloud config setup correctly +* A service account created +* The appropriate permissions assigned to the service account +* A key file created to enable the use of Google Cloud automation. -The as-is file from the git repository will not work without modifying the _main.tf_ to the values relevant to your project. +This will take about 60 seconds. At the end you will see output about permissions and the configuration of the account. +## Run terraform + +When this is complete, you can initialize teraform to begin your cluster creations: +``` +terraform init +``` +A correct result will contain: +``` +Terraform has been successfully initialized! +``` +Some terraform commands are wrapped in a makefile, so you can now create your cluster: +``` +make apply ``` -variable "cluster_name" { - type = string - default = "condor" - description = "Name used to prefix resources in cluster." - -} +A successful run will show: +``` +Apply complete! Resources: 4 added, 0 changed, 0 destroyed. +``` +## Connect to the Submit node for HTCondor +You will now be able list the VMs created: +``` +gcloud compute instances list +``` +You will log in to the `submit` node: +``` +gcloud compute ssh c-submit +``` +Now you are logged in to your HTCondor cluster. -module "htcondor" { - source = "./htcondor/" - cluster_name = var.cluster_name - osimage = "lsst" - osversion = "7" - zone="[[YOUR_ZONE]]" - project="[[PROJECT_ID]]" - max_replicas=20 - min_replicas=1 - service_account="htcondor@[[PROJECT_ID]].iam.gserviceaccount.com" - use_preemptibles=true -} +### Checking the status +You can verify if the HTCondor install is completed: ``` +condor_q +``` +You will see output: +``` +-- Schedd: c-submit.c.quantum-htcondor-14.internal : <10.150.0.2:9618?... @ 08/18/21 18:37:50 +OWNER BATCH_NAME SUBMITTED DONE RUN IDLE HOLD TOTAL JOB_IDS + +Total for query: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended +Total for drj: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended +Total for all users: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended +``` +If you get `command not found`, you will need to wait a few minutes for the HTCondor install to complete. + +## Clone the repo on your cluster + +Again on the `submit` node you you can install the repo to get access to previously created submission files: +``` +git clone https://github.com/jrossthomson/qsim.git +``` +Then cd to the tutorial directory. +``` +cd qsim/docs/tutorials/multinode +``` +Now it is possible to submit a job: +``` +condor_submit circuit_q24.sub +``` +If successful, the output will be: +``` +Submitting job(s). +1 job(s) submitted to cluster 1. +``` +This may take a few minutes, but when completed the command: +``` +ls out +``` +will list the files: +``` +err.1-0 log.1-0 out.1-0 placeholder +``` +You can also see the progress of the job throught the log file: +``` +tail -f out/log.1-0 +``` +After the job s completed, the ouput of the job can be seen: +``` +cat out/out.1-0 +``` +To run multiple simulations, you can change the submit file by editing the file `circuit_q24.sub`: +``` +universe = docker +docker_image = gcr.io/quantum-builds/github.com/quantumlib/qsim +arguments = -c circuit_q24 +transfer_input_files = ../../../circuits/circuit_q24 +should_transfer_files = YES +when_to_transfer_output = ON_EXIT +output = out/out.$(Cluster)-$(Process) +error = out/err.$(Cluster)-$(Process) +log = out/log.$(Cluster)-$(Process) +request_memory = 1GB +queue 1 +``` +Change the final line to `queue 100`. Then submit the job again: +``` +condor_submit circuit_q24.sub +``` +Now many jobs will be submitted. + From 95d778c802c4848767a76d172b2453fa2fc98816 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Wed, 18 Aug 2021 19:17:05 +0000 Subject: [PATCH 063/246] UPdated both readmes --- docs/tutorials/multinode/README.md | 294 ++++++------------- docs/tutorials/multinode/terraform/README.md | 7 + 2 files changed, 93 insertions(+), 208 deletions(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index cbe78f00..31fd2c03 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -1,279 +1,157 @@ -# Run a multinode quantum simulation +# Multinode quantum simulation using HTCondor on GCP +This tutorial will take you through the process of running many qsim simulations +on Google Cloud. In some situations, it is required to run many instances of the same +simulation. This could be used to provide a parameter sweep or to evaluation noise characteristics. + ## Objectives * Use Terraform to deploy a HTCondor cluster * Run a multinode simulation using HTCondor * Query cluster information and monitor running jobs in HTCondor -* Autoscale nodes to runt jobs that new more Simulation capacit - -## Costs - -This tutorial uses the following billable components of Google Cloud: - -* [Compute Engine](https://cloud.google.com/compute/all-pricing) - -To generate a cost estimate based on your projected usage, use the [pricing calculator](https://cloud.google.com/products/calculator). -When you finish this tutorial, you can avoid continued billing by deleting the resources -you created. For more information, see Cleaning up. - -> Note: the resources created in this tutorial will persist until deleted or the project is deleted. Google Cloud will charge for this time. - - -## Before you begin -1. In the Google Cloud Console, on the project selector page, select or create a Google Cloud project. To make this easy, try to choose a project where you are _Owner_ or _Editor_. - > Note: If you don't plan to keep the resources that you create in this procedure, create a project instead of selecting an existing project. After you finish these steps, you can delete the project, removing all resources associated with the project. -Go to [project selector](https://console.cloud.google.com/projectselector2/home/dashboard) -1. Make sure that billing is enabled for your Cloud project. Learn how to confirm that billing is enabled for your project. -1. Enable the [Compute Engine and Deployment Manager APIs](https://console.cloud.google.com/flows/enableapi?apiid=compute,deploymentmanager.googleapis.com). -1. In the Cloud Console, [activate Cloud Shell](https://console.cloud.google.com/?cloudshell=true) +## Create a project +In a seperate tutorial the method to create Google Cloud project is explained in detail. +[Please refer to this tutorial for guidance](https://quantumai.google/qsim/tutorials/qsimcirq_gcp#gcp_setup). -At the bottom of the Cloud Console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Cloud SDK already installed, including the gcloud command-line tool, and with values already set for your current project. It can take a few seconds for the session to initialize. +When you have a project created, move on to the next step. -## Setting up your environment in Cloud Shell -Select a region and zone for your cluster to run. [This guide can help](https://cloud.google.com/compute/docs/regions-zones). - -Once selected, define the following environment variables in Cloud Shell. +## Configure your enviroment +In your procject using the Cloud Console(https://console.google.com/home/dashboard?cloudshell=true), clone this Github repo. ``` -export TF_VAR_project=[[YOUR_PROJECT_ID]] -export TF_VAR_region=[[YOUR_REGION]] -export TF_VAR_zone=[[YOUR_ZONE]] +git clone https://github.com/jrossthomson/qsim.git ``` -With those variables defined, you can configure `gcloud`. Cut and paste the gcloud config commands in your Cloud Shell. +Change directory to the tutorial: ``` -gcloud config set project $TF_VAR_project -gcloud config set compute/region $TF_VAR_region -gcloud config set compute/zone $TF_VAR_zone +cd qsim/docs/tutorials/multinode/terraform ``` -With that completed, you can create a service account that will run the HTCondor cluster. +This is where you will use terraform to create the HTCondor cluster required to run your jobs. + +### Edit init file to setup environment +You can now edit `init.sh` to change the name of the project you are using. You can also change the zone and region as you see fit. More information is available [here](https://cloud.google.com/compute/docs/regions-zones). + +Change the variables to reflect your project, most important is the first line: ``` -gcloud iam service-accounts create htcondor +export TF_VAR_project=my-quantum-htcondor-project ``` -Then give the service accounts the permissions that will allow HTCondor to run correctly. +The project id needs to be globally unique and to be the same as the project you just created. + +The edited `init.sh` file should be "sourced" in the cloud shell: + ``` -gcloud projects add-iam-policy-binding ${TF_VAR_project} --role roles/iam.serviceAccountUser \ - --member="serviceAccount:htcondor@${TF_VAR_project}.iam.gserviceaccount.com" -gcloud projects add-iam-policy-binding ${TF_VAR_project} --role roles/compute.admin \ ---member="serviceAccount:htcondor@${TF_VAR_project}.iam.gserviceaccount.com" -gcloud projects add-iam-policy-binding ${TF_VAR_project} --role roles/monitoring.admin \ ---member="serviceAccount:htcondor@${TF_VAR_project}.iam.gserviceaccount.com" +source init.sh ``` +Repsond in the affirmative to any pop-ups that request permissions on the Cloud platform. -## Clone the repository and build the cluster +The final outcome of this script will include: -The `qsim` repository has all the code required to create the repository and to run the simulations. +* A gcloud config setup correctly +* A service account created +* The appropriate permissions assigned to the service account +* A key file created to enable the use of Google Cloud automation. -1. Clone the repo and change to the working directory. - ``` - git clone https://github.com/jrossthomson/qsim.git - cd qsim/docs/tutorials/multinode/terraform - ``` +This will take about 60 seconds. At the end you will see output about permissions and the configuration of the account. -1. Build the cluster with Terraform - ``` - terraform init - make apply - ``` +## Run terraform -This process will create 3 VMs instances and an autoscaling Managed Instance Group. To see the instances use the glcoud command in the Cloud Shell. +When this is complete, you can initialize teraform to begin your cluster creations: ``` -gcloud compute instances list +terraform init ``` -The output should be something like the output here: +A correct result will contain: ``` -NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS -c-8xlg us-east4-c n2-standard-16 10.150.0.21 34.86.120.132 RUNNING -c-manager us-east4-c n2-standard-4 10.150.0.16 35.188.250.134 RUNNING -c-submit us-east4-c n2-standard-4 10.150.0.17 35.245.189.252 RUNNING +Terraform has been successfully initialized! ``` - -The Mangaged Instance Group can be seen with gcloud. +Some terraform commands are wrapped in a makefile, so you can now create your cluster: ``` -gcloud compute instance-groups list +make apply ``` -And the output like the following. +A successful run will show: ``` -NAME LOCATION SCOPE NETWORK MANAGED INSTANCES -c us-east4-c zone default Yes 1 +Apply complete! Resources: 4 added, 0 changed, 0 destroyed. ``` -## Run a job on the cluster -The next step is to run a job on the HTCondor cluster. Jobs are submitted on the `c-submit` VM instance known as the Submit Node in HTCondor. You connect to the Submit Node using gcloud SSH. +## Connect to the Submit node for HTCondor +You will now be able list the VMs created: ``` -gcloud compute ssh c-submit +gcloud compute instances list ``` -There will now be a prompt with something like `username@c-submit`. - -For convenience there is prepared HTCondor job submission files in the Github repo. To get these, -clone the repository on the Submit Node, and change to the tutorial directory. +You will log in to the `submit` node: ``` - git clone https://github.com/jrossthomson/qsim.git - cd qsim/docs/tutorials/multinode +gcloud compute ssh c-submit ``` +Now you are logged in to your HTCondor cluster. -It is very likely that the HTCondor installation is not completely finished at this point: it takes several minutes. The `condor_status` command will tell you if the cluster is ready to run jobs. - +### Checking the status +You can verify if the HTCondor install is completed: ``` -condor_status +condor_q ``` -The output from the command should resemble this. +You will see output: ``` -Name OpSys Arch State Activity LoadAv Mem ActvtyTime - -slot1@c-6klg.c.test-quantum-multinode.internal LINUX X86_64 Unclaimed Idle 0.000 64263 0+00:19:33 - - Machines Owner Claimed Unclaimed Matched Preempting Drain +-- Schedd: c-submit.c.quantum-htcondor-14.internal : <10.150.0.2:9618?... @ 08/18/21 18:37:50 +OWNER BATCH_NAME SUBMITTED DONE RUN IDLE HOLD TOTAL JOB_IDS - X86_64/LINUX 1 0 0 1 0 0 0 - - Total 1 0 0 1 0 0 0 +Total for query: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended +Total for drj: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended +Total for all users: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended ``` +If you get `command not found`, you will need to wait a few minutes for the HTCondor install to complete. -If the output shows "Unclaimed" machines, you are ready to submit a job with HTCondor. +## Clone the repo on your cluster +Again on the `submit` node you you can install the repo to get access to previously created submission files: +``` +git clone https://github.com/jrossthomson/qsim.git +``` +Then cd to the tutorial directory. +``` +cd qsim/docs/tutorials/multinode ``` -condor_submit circuit_q30.sub +Now it is possible to submit a job: ``` -There should be a response indicating the job was submitted. +condor_submit circuit_q24.sub +``` +If successful, the output will be: ``` Submitting job(s). 1 job(s) submitted to cluster 1. ``` -Now you can see if the job is running correctly. +This may take a few minutes, but when completed the command: ``` -condor_q +ls out ``` -The output will be similar to the snippet below. +will list the files: ``` --- Schedd: c-submit.c.test-quantum-multinode.internal : <10.150.0.6:9618?... @ 05/20/21 15:04:24 -OWNER BATCH_NAME SUBMITTED DONE RUN IDLE TOTAL JOB_IDS -jrossthomson ID: 1 5/20 15:04 _ 1 _ 1 1.0 - -Total for query: 1 jobs; 0 completed, 0 removed, 0 idle, 1 running, 0 held, 0 suspended -Total for jrossthomson: 1 jobs; 0 completed, 0 removed, 0 idle, 1 running, 0 held, 0 suspended -Total for all users: 1 jobs; 0 completed, 0 removed, 0 idle, 1 running, 0 held, 0 suspended +err.1-0 log.1-0 out.1-0 placeholder ``` -When this is completed you should see output in the `out` directory. +You can also see the progress of the job throught the log file: ``` -ls out/ -err.1-0 log.1-0 out.1-0 +tail -f out/log.1-0 ``` -The contents of `out.1-0` will have the content you are expecting from the simulation. This will take about 10 minutes to be complete. +After the job s completed, the ouput of the job can be seen: ``` cat out/out.1-0 -000: -4.734056e-05 1.2809795e-05 2.4052194e-09 -001: -3.6258607e-06 2.3642724e-07 1.3202764e-11 -010: -2.9523137e-05 2.280164e-05 1.3915304e-09 -011: -1.3954962e-05 9.4717652e-06 2.8445529e-10 -100: -6.8555892e-06 -6.7632163e-07 4.7456514e-11 -101: -2.0390624e-05 3.1813841e-05 1.4278979e-09 -110: 1.5711608e-05 -7.5214862e-06 3.0342737e-10 -111: 9.2345472e-06 -2.7716227e-05 8.5346613e-10 -``` -## Sumitting many jobs - -The job just submitted only ran a single instance of a qsim simulation. The main purpose of the present study is to -run many (up to thousands of) simulations to provide a broad study with statistical significance. - -A simple way to submit many jobs is to use the `queue` command in the HTCondor. For this step edit the file `circuit_q30.sub` -in your favorite editor, such as vim or nano. You will see the full submission file. ``` -universe = docker/jross - +To run multiple simulations, you can change the submit file by editing the file `circuit_q24.sub`: +``` +universe = docker docker_image = gcr.io/quantum-builds/github.com/quantumlib/qsim -arguments = -c circuit_q30 -transfer_input_files = ../../../circuits/circuit_q30 +arguments = -c circuit_q24 +transfer_input_files = ../../../circuits/circuit_q24 should_transfer_files = YES when_to_transfer_output = ON_EXIT output = out/out.$(Cluster)-$(Process) error = out/err.$(Cluster)-$(Process) log = out/log.$(Cluster)-$(Process) -request_memory = 10GB +request_memory = 1GB queue 1 ``` -The submission file is running the public qsim `docker image` from [QuantumAI](https://quantumai.google/qsim). -The command that is running utimately is running the `qsim` base executable. -``` -qsim_base.x -c circuit_q30 -``` -This is achieved by running the _docker_image_ as listed with the `arguments` "-c circuit_q30". The -circuit file `circuit_q30` is transferred to the compute nodes via the `transfer_input_files` command. - -> If you are interested -> in an introduction to HTCondor in general, there is a -> [great introduction from CERN](https://indico.cern.ch/event/611296/contributions/2604376/attachments/1471164/2276521/TannenbaumT_UserTutorial.pdf). For details on job submission syntax there is a section of the [HTCondor Manual](https://htcondor.readthedocs.io/en/latest/users-manual/submitting-a-job.html) dedicated to this. - -To expand the simulation to run 20 instances of the docker image, the change required is to modify the `queue` command. -``` -queue 20 -``` -Now when you run the `condor_submit` command, -``` -condor_submit circuit_q30.sub -``` -20 jobs will be visible from the `condor_q` command. - -The really cool part is that now when you look at the list of VM instance you see that the system has -automatically scaled up the number of VM machines to support the 20 jobs you have requested to run, or as we normally refer to it, -performs __autoscaling__. - -``` -gcloud compute instances list -NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS -c-2q8w us-east4-c n2-standard-16 10.150.0.15 35.245.204.243 RUNNING -c-40dz us-east4-c n2-standard-16 10.150.0.11 34.86.33.206 RUNNING -c-4x4g us-east4-c n2-standard-16 10.150.0.14 35.245.201.254 RUNNING -c-6klg us-east4-c n2-standard-16 10.150.0.8 34.86.161.51 RUNNING -c-748q us-east4-c n2-standard-16 10.150.0.12 35.245.74.106 RUNNING -c-dgt0 us-east4-c n2-standard-16 10.150.0.13 34.86.102.113 RUNNING -c-manager us-east4-c n2-standard-4 10.150.0.7 35.221.59.182 RUNNING -c-submit us-east4-c n2-standard-4 10.150.0.6 35.245.203.14 RUNNING -c-vdsz us-east4-c n2-standard-16 10.150.0.10 35.236.211.189 RUNNING -c-x4bd us-east4-c n2-standard-16 10.150.0.9 34.86.246.195 RUNNING -``` - -When all these 20 jobs are complete, you can see the output in the `out` directory. -``` -ls out/out.* -out/out.2-0 out/out.2-10 out/out.2-12 out/out.2-14 out/out.2-16 out/out.2-18 out/out.2-2 out/out.2-4 out/out.2-6 out/out.2-8 -out/out.2-1 out/out.2-11 out/out.2-12 out/out.2-15 out/out.2-17 out/out.2-19 out/out.2-2 out/out.2-5 out/out.2-7 out/out.2-9 - -``` -The output of the simulation is in these files. - -Finally, after the system has been idle for several minutes, you will see that the number of VM instances with autoscale -back to a single Compute Node, the Manager Node and the Submit node. This helps to control costs by removing VMs when -you do not need them. - -## Next steps -Further work here will allow you to run multiple simulations for different work. There are several things to look at. - -* Creating and running with your own container -* Running with multiple input files -* Selecting different configurations of the submit file - -## Cleaning up -The easiest way to eliminate billing is to delete the Cloud project you created for the tutorial. Alternatively, -you can delete the individual resources. - -### Delete the project -> __Caution:__ Deleting a project has the following effects: -> Everything in the project is deleted. If you used an existing project for this tutorial, when you delete it, you also delete any other work you've done in the project. -> Custom project IDs are lost. When you created this project, you might have created a custom project ID that you want to use in the future. To preserve the URLs that use the project ID, such as an appspot.com URL, delete selected resources inside the project instead of deleting the whole project. - -1. In the Cloud Console, go to the Manage resources page. - > Go to [Manage resources](https://console.cloud.google.com/iam-admin/projects) - -1. In the project list, select the project that you want to delete, and then click Delete. -1. In the dialog, type the project ID, and then click Shut down to delete the project. - -### Delete the Slurm cluster -The second option is to delete the HTCondor cluster. In the `qsim/docs/tutorials/multinode/terraform` directory, run the make command. +Change the final line to `queue 100`. Then submit the job again: ``` - make destroy +condor_submit circuit_q24.sub ``` -This will remove the HTCondor cluster. +Now many jobs will be submitted. diff --git a/docs/tutorials/multinode/terraform/README.md b/docs/tutorials/multinode/terraform/README.md index b4464ddf..31fd2c03 100644 --- a/docs/tutorials/multinode/terraform/README.md +++ b/docs/tutorials/multinode/terraform/README.md @@ -3,6 +3,13 @@ This tutorial will take you through the process of running many qsim simulations on Google Cloud. In some situations, it is required to run many instances of the same simulation. This could be used to provide a parameter sweep or to evaluation noise characteristics. +## Objectives + +* Use Terraform to deploy a HTCondor cluster +* Run a multinode simulation using HTCondor +* Query cluster information and monitor running jobs in HTCondor + + ## Create a project In a seperate tutorial the method to create Google Cloud project is explained in detail. [Please refer to this tutorial for guidance](https://quantumai.google/qsim/tutorials/qsimcirq_gcp#gcp_setup). From 51cc80cc1f35b80bccc9b68e833c6815f5712d58 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 19 Aug 2021 08:47:42 -0700 Subject: [PATCH 064/246] Prevent unused variable error. --- tests/mps_simulator_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mps_simulator_test.cc b/tests/mps_simulator_test.cc index 0bbff966..1aa7def7 100644 --- a/tests/mps_simulator_test.cc +++ b/tests/mps_simulator_test.cc @@ -26,7 +26,7 @@ namespace mps { namespace { TEST(MPSSimulator, Create) { - auto sim = MPSSimulator(1); + MPSSimulator(1); } TEST(MPSSimulator, Apply1RightArbitrary) { From d522d954ba3d363d71cedb513282026b048a2ea2 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 19 Aug 2021 09:20:02 -0700 Subject: [PATCH 065/246] cuda_library --- lib/BUILD | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/BUILD b/lib/BUILD index 3316f531..26d93c72 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -2,7 +2,14 @@ package(default_visibility = ["//visibility:public"]) ##### Aggregate libraries ##### +# Libraries of the following form: +# # cuda_library +# cc_library(...) +# are converted to cuda_library rules when imported to the Google codebase. +# Do not modify this tag. + # Full qsim library +# cuda_library cc_library( name = "qsim_lib", hdrs = [ @@ -171,6 +178,7 @@ cc_library( hdrs = ["util.h"], ) +# cuda_library cc_library( name = "util_cuda", hdrs = ["util_cuda.h"], @@ -331,6 +339,7 @@ cc_library( hdrs = ["vectorspace.h"], ) +# cuda_library cc_library( name = "vectorspace_cuda", hdrs = ["vectorspace_cuda.h"], @@ -384,6 +393,7 @@ cc_library( ], ) +# cuda_library cc_library( name = "statespace_cuda", hdrs = [ @@ -435,6 +445,7 @@ cc_library( ], ) +# cuda_library cc_library( name = "simulator_cuda", hdrs = [ From ba2ce80dadf71a89c23a9d1a518d7bd2444ed95c Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 19 Aug 2021 09:37:12 -0700 Subject: [PATCH 066/246] Move cuda_library note --- lib/BUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/BUILD b/lib/BUILD index 26d93c72..2b1288a8 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -1,13 +1,13 @@ package(default_visibility = ["//visibility:public"]) -##### Aggregate libraries ##### - # Libraries of the following form: # # cuda_library # cc_library(...) # are converted to cuda_library rules when imported to the Google codebase. # Do not modify this tag. +##### Aggregate libraries ##### + # Full qsim library # cuda_library cc_library( From e15c1688f1ca89746b0b4867c6a279c41a103e79 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Thu, 19 Aug 2021 21:22:07 +0000 Subject: [PATCH 067/246] Added noise sims --- docs/tutorials/multinode/noise.py | 51 ++++++++++++++++++++ docs/tutorials/multinode/noise.sub | 11 +++++ docs/tutorials/multinode/noise2.py | 18 +++++++ docs/tutorials/multinode/noise3.py | 22 +++++++++ docs/tutorials/multinode/terraform/README.md | 14 +++--- docs/tutorials/multinode/terraform/init.sh | 2 +- 6 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 docs/tutorials/multinode/noise.py create mode 100644 docs/tutorials/multinode/noise.sub create mode 100644 docs/tutorials/multinode/noise2.py create mode 100644 docs/tutorials/multinode/noise3.py diff --git a/docs/tutorials/multinode/noise.py b/docs/tutorials/multinode/noise.py new file mode 100644 index 00000000..ecbd751d --- /dev/null +++ b/docs/tutorials/multinode/noise.py @@ -0,0 +1,51 @@ +import cirq +import qsimcirq +import time + +q0, q1 = cirq.LineQubit.range(2) + +circuit = cirq.Circuit( + # Perform a Hadamard on both qubits + cirq.H(q0), cirq.H(q1), + # Apply amplitude damping to q0 with probability 0.1 + cirq.amplitude_damp(gamma=0.1).on(q0), + # Apply phase damping to q1 with probability 0.1 + cirq.phase_damp(gamma=0.1).on(q1), +) +qsim_simulator = qsimcirq.QSimSimulator() +results = qsim_simulator.simulate(circuit) +print(results.final_state_vector) + + +# Simulate measuring at the end of the circuit. +measured_circuit = circuit + cirq.measure(q0, q1, key='m') +measure_results = qsim_simulator.run(measured_circuit, repetitions=5) +print(measure_results) + +# Calculate only the amplitudes of the |00) and |01) states. +amp_results = qsim_simulator.compute_amplitudes( + circuit, bitstrings=[0b00, 0b01]) +print(amp_results) + +# Calculate only the amplitudes of the |00) and |01) states. +amp_results = qsim_simulator.compute_amplitudes( + circuit, bitstrings=[0b00, 0b01]) +print(amp_results) + + +# Set the "noisy repetitions" to 100. +# This parameter only affects expectation value calculations. +options = {'r': 100} +# Also set the random seed to get reproducible results. +seed = int(time.time()) +print(seed) +ev_simulator = qsimcirq.QSimSimulator(qsim_options=options, seed=seed) +# Define observables to measure: for q0 and for q1. +pauli_sum1 = cirq.Z(q0) +pauli_sum2 = cirq.X(q1) +# Calculate expectation values for the given observables. +ev_results = ev_simulator.simulate_expectation_values( + circuit, + observables=[pauli_sum1, pauli_sum2], +) +print(ev_results) \ No newline at end of file diff --git a/docs/tutorials/multinode/noise.sub b/docs/tutorials/multinode/noise.sub new file mode 100644 index 00000000..cccc85b0 --- /dev/null +++ b/docs/tutorials/multinode/noise.sub @@ -0,0 +1,11 @@ +universe = docker +docker_image = gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest +arguments = python3 noise3.py +should_transfer_files = YES +transfer_input_files = noise3.py +when_to_transfer_output = ON_EXIT +output = out/out.$(Cluster)-$(Process) +error = out/err.$(Cluster)-$(Process) +log = out/log.$(Cluster)-$(Process) +request_memory = 10GB +queue 50 \ No newline at end of file diff --git a/docs/tutorials/multinode/noise2.py b/docs/tutorials/multinode/noise2.py new file mode 100644 index 00000000..db593b7a --- /dev/null +++ b/docs/tutorials/multinode/noise2.py @@ -0,0 +1,18 @@ +import cirq +import qsimcirq +import time + +circuit = cirq.testing.random_circuit( + qubits=3, n_moments=3, op_density=1, random_state=11 +) + +# Display the noiseless circuit. +print("Circuit without noise:") +print(circuit) + +# Add noise to the circuit. +noisy = circuit.with_noise(cirq.depolarize(p=0.01)) + +# Display it. +print("\nCircuit with noise:") +print(noisy) \ No newline at end of file diff --git a/docs/tutorials/multinode/noise3.py b/docs/tutorials/multinode/noise3.py new file mode 100644 index 00000000..38d741a3 --- /dev/null +++ b/docs/tutorials/multinode/noise3.py @@ -0,0 +1,22 @@ +import cirq, qsimcirq + +# Create a Bell state, |00) + |11) +q0, q1 = cirq.LineQubit.range(2) +circuit = cirq.Circuit( + cirq.H(q0), + cirq.CNOT(q0, q1), + cirq.measure(q0, q1, key='m') +) + +# Constructs a noise model that adds depolarizing noise after each gate. +noise = cirq.NoiseModel.from_noise_model_like(cirq.depolarize(p=0.05)) + +# Use the noise model to create a noisy circuit. +noisy_circuit = cirq.Circuit(noise.noisy_moments(circuit, system_qubits=[q0, q1])) + +sim = qsimcirq.QSimSimulator() +result = sim.run(noisy_circuit, repetitions=1000) +# Outputs a histogram dict of result:count pairs. +# Expected result is a bunch of 0s and 3s, with fewer 1s and 2s. +# (For comparison, the noiseless circuit will only have 0s and 3s) +print(result.histogram(key='m')) \ No newline at end of file diff --git a/docs/tutorials/multinode/terraform/README.md b/docs/tutorials/multinode/terraform/README.md index 31fd2c03..443f1271 100644 --- a/docs/tutorials/multinode/terraform/README.md +++ b/docs/tutorials/multinode/terraform/README.md @@ -18,7 +18,7 @@ When you have a project created, move on to the next step. ## Configure your enviroment -In your procject using the Cloud Console(https://console.google.com/home/dashboard?cloudshell=true), clone this Github repo. +In your project using the Cloud Console(https://console.google.com/home/dashboard?cloudshell=true), clone this Github repo. ``` git clone https://github.com/jrossthomson/qsim.git ``` @@ -135,19 +135,19 @@ After the job s completed, the ouput of the job can be seen: ``` cat out/out.1-0 ``` -To run multiple simulations, you can change the submit file by editing the file `circuit_q24.sub`: +To run multiple simulations, you can run the submit file `noise.sub`: ``` universe = docker -docker_image = gcr.io/quantum-builds/github.com/quantumlib/qsim -arguments = -c circuit_q24 -transfer_input_files = ../../../circuits/circuit_q24 +docker_image = gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest +arguments = python3 noise3.py should_transfer_files = YES +transfer_input_files = noise3.py when_to_transfer_output = ON_EXIT output = out/out.$(Cluster)-$(Process) error = out/err.$(Cluster)-$(Process) log = out/log.$(Cluster)-$(Process) -request_memory = 1GB -queue 1 +request_memory = 10GB +queue 50 ``` Change the final line to `queue 100`. Then submit the job again: ``` diff --git a/docs/tutorials/multinode/terraform/init.sh b/docs/tutorials/multinode/terraform/init.sh index 82461e55..0d10befe 100644 --- a/docs/tutorials/multinode/terraform/init.sh +++ b/docs/tutorials/multinode/terraform/init.sh @@ -1,4 +1,4 @@ -export TF_VAR_project=quantum-htcondor-13 +export TF_VAR_project=quantum-htcondor-15 export TF_VAR_project_id=us-east4-c export TF_VAR_zone=us-east4-c export TF_VAR_region=us-east4 From 47ec0b077ab8614500a499641843efe89ba99413 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Thu, 19 Aug 2021 21:45:58 +0000 Subject: [PATCH 068/246] Updated docs. --- docs/tutorials/multinode/terraform/README.md | 43 ++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/multinode/terraform/README.md b/docs/tutorials/multinode/terraform/README.md index 443f1271..5459045a 100644 --- a/docs/tutorials/multinode/terraform/README.md +++ b/docs/tutorials/multinode/terraform/README.md @@ -135,6 +135,7 @@ After the job s completed, the ouput of the job can be seen: ``` cat out/out.1-0 ``` +## Running noisy simulations To run multiple simulations, you can run the submit file `noise.sub`: ``` universe = docker @@ -149,9 +150,45 @@ log = out/log.$(Cluster)-$(Process) request_memory = 10GB queue 50 ``` -Change the final line to `queue 100`. Then submit the job again: +The final line in this submit file has `queue 50`. This means 50 instances of this simulation will be run. The job can be submitted with the `condor_submit` command. ``` -condor_submit circuit_q24.sub +condor_submit noise.sub +``` +The output will look as follows: +``` +Submitting job(s).................................................. +50 job(s) submitted to cluster 2. +``` +If this is the second _submit_ you have run, you can see the output of the all the simualtions. The output will be in the `out` directory. +``` +cat out/out.2-* +``` +You can see the results of the simulations. +``` +Counter({3: 462, 0: 452, 2: 50, 1: 36}) +Counter({0: 475, 3: 435, 1: 49, 2: 41}) +Counter({0: 450, 3: 440, 1: 59, 2: 51}) +Counter({0: 459, 3: 453, 2: 51, 1: 37}) +Counter({3: 471, 0: 450, 2: 46, 1: 33}) +Counter({3: 467, 0: 441, 1: 54, 2: 38}) +Counter({3: 455, 0: 455, 1: 50, 2: 40}) +Counter({3: 466, 0: 442, 2: 51, 1: 41}) +. +. +. ``` -Now many jobs will be submitted. +Note that because this is a noise driven circuit, the results of each simulation are different. + +To run your own simulations, simply create a noisy circuit in your _qsim_ python file. + +There are many more examples of circuits to be run [here](https://quantumai.google/cirq/noise) + +## Shutting down +When you are done with this tutorial, it is important to remove the resources. You can do this with _terraform_ +``` +make destroy +``` +> The most effective way to ensure you are not charged is to delete the project. [The instructions are here.](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) + + From d36b26c47b81959b90a45f3958206ce934487c80 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Thu, 19 Aug 2021 21:48:47 +0000 Subject: [PATCH 069/246] Duplicating readme files --- docs/tutorials/multinode/README.md | 58 ++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index 31fd2c03..28a81271 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -1,3 +1,4 @@ + # Multinode quantum simulation using HTCondor on GCP This tutorial will take you through the process of running many qsim simulations on Google Cloud. In some situations, it is required to run many instances of the same @@ -18,7 +19,7 @@ When you have a project created, move on to the next step. ## Configure your enviroment -In your procject using the Cloud Console(https://console.google.com/home/dashboard?cloudshell=true), clone this Github repo. +In your project using the Cloud Console(https://console.google.com/home/dashboard?cloudshell=true), clone this Github repo. ``` git clone https://github.com/jrossthomson/qsim.git ``` @@ -135,23 +136,60 @@ After the job s completed, the ouput of the job can be seen: ``` cat out/out.1-0 ``` -To run multiple simulations, you can change the submit file by editing the file `circuit_q24.sub`: +## Running noisy simulations +To run multiple simulations, you can run the submit file `noise.sub`: ``` universe = docker -docker_image = gcr.io/quantum-builds/github.com/quantumlib/qsim -arguments = -c circuit_q24 -transfer_input_files = ../../../circuits/circuit_q24 +docker_image = gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest +arguments = python3 noise3.py should_transfer_files = YES +transfer_input_files = noise3.py when_to_transfer_output = ON_EXIT output = out/out.$(Cluster)-$(Process) error = out/err.$(Cluster)-$(Process) log = out/log.$(Cluster)-$(Process) -request_memory = 1GB -queue 1 +request_memory = 10GB +queue 50 ``` -Change the final line to `queue 100`. Then submit the job again: +The final line in this submit file has `queue 50`. This means 50 instances of this simulation will be run. The job can be submitted with the `condor_submit` command. ``` -condor_submit circuit_q24.sub +condor_submit noise.sub +``` +The output will look as follows: +``` +Submitting job(s).................................................. +50 job(s) submitted to cluster 2. +``` +If this is the second _submit_ you have run, you can see the output of the all the simualtions. The output will be in the `out` directory. +``` +cat out/out.2-* +``` +You can see the results of the simulations. +``` +Counter({3: 462, 0: 452, 2: 50, 1: 36}) +Counter({0: 475, 3: 435, 1: 49, 2: 41}) +Counter({0: 450, 3: 440, 1: 59, 2: 51}) +Counter({0: 459, 3: 453, 2: 51, 1: 37}) +Counter({3: 471, 0: 450, 2: 46, 1: 33}) +Counter({3: 467, 0: 441, 1: 54, 2: 38}) +Counter({3: 455, 0: 455, 1: 50, 2: 40}) +Counter({3: 466, 0: 442, 2: 51, 1: 41}) +. +. +. ``` -Now many jobs will be submitted. +Note that because this is a noise driven circuit, the results of each simulation are different. + +To run your own simulations, simply create a noisy circuit in your _qsim_ python file. + +There are many more examples of circuits to be run [here](https://quantumai.google/cirq/noise) + +## Shutting down +When you are done with this tutorial, it is important to remove the resources. You can do this with _terraform_ +``` +make destroy +``` +> The most effective way to ensure you are not charged is to delete the project. [The instructions are here.](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) + + From 7792aec4290f2973937f757b209c82b913257f94 Mon Sep 17 00:00:00 2001 From: Laurynas Tamulevicius Date: Thu, 19 Aug 2021 22:53:32 +0100 Subject: [PATCH 070/246] Update the installation docs Change installation docs according to work on cross-platform support. --- docs/install_qsimcirq.md | 45 +++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/docs/install_qsimcirq.md b/docs/install_qsimcirq.md index 9fe69d4e..82cfc0d5 100644 --- a/docs/install_qsimcirq.md +++ b/docs/install_qsimcirq.md @@ -1,49 +1,52 @@ # Installing qsimcirq -The qsim-Cirq Python interface is available as a PyPI package for Linux users. -For all other users, Dockerfiles are provided to install qsim in a contained +The qsim-Cirq Python interface is available as a PyPI package for Linux, MacOS and Windows users. +For all others, Dockerfiles are provided to install qsim in a contained environment. **Note:** The core qsim library (under [lib/](https://github.com/quantumlib/qsim/blob/master/lib)) can be included directly in C++ code without installing this interface. -## Linux installation +## Before installation Prior to installation, consider opening a [virtual environment](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/). -The qsim-Cirq interface uses [CMake](https://cmake.org/) to ensure stable -compilation of its C++ libraries across a variety of Linux distributions. -CMake can be installed from their website, or with the command -`apt-get install cmake`. - -Other prerequisites (including pybind11 and pytest) are included in the +Prerequisites are included in the [`requirements.txt`](https://github.com/quantumlib/qsim/blob/master/requirements.txt) file, and will be automatically installed along with qsimcirq. -To install the qsim-Cirq interface on Linux, simply run `pip3 install qsimcirq`. -For examples of how to use this package, see the tests in -[qsim/qsimcirq_tests/](https://github.com/quantumlib/qsim/blob/master/qsimcirq_tests/). +## Linux installation + +We provide `qsimcirq` Python wheels on 64-bit `x86` architectures with `Python 3.{5,6,7,8,9}`. + +Simply run `pip3 install qsimcirq`. -## MacOS and Windows installation +## MacOS installation -For users interested in running qsim on a MacOS or Windows device, we strongly -recommend using the [Docker config](./docker.md) provided with this -repository. +We provide `qsimcirq` Python wheels on `x86` architectures with `Python 3.{5,6,7,8,9}`. -### Experimental install process +Simply run `pip3 install qsimcirq`. -Alternatively, MacOS and Windows users can follow the Linux install process, -but it is currently untested on those platforms. Users are encouraged to report -any issues seen with this process. +## Windows installation + +We provide `qsimcirq` Python wheels on 64-bit `x86` and `amd64` architectures with `Python 3.{5,6,7,8,9}`. + +Simply run `pip3 install qsimcirq`. + +## There's no compatible wheel for my machine! + +If existing wheels do no meet your needs please open an issue with your machine configuration (i.e. CPU architecture, Python version) and consider using the [Docker config](./docker.md) provided with this repository. ## Testing -After installing qsimcirq on your machine, you can test the installation by +After installing `qsimcirq` on your machine, you can test the installation by copying [qsimcirq_tests/qsimcirq_test.py](qsimcirq_tests/qsimcirq_test.py) to your machine and running `python3 -m pytest qsimcirq_test.py`. +It also has examples of how how to use this package. + **Note:** Because of how Python searches for modules, the test file cannot be run from inside a clone of the qsim repository, or from any parent directory of such a repository. Failure to meet this criteria may result From 3782b02b882ea80bf1d099c0492e6b1f8d65600e Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 20 Aug 2021 06:33:06 -0700 Subject: [PATCH 071/246] Split qsim_lib into CUDA/CC versions --- lib/BUILD | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/lib/BUILD b/lib/BUILD index 2b1288a8..99c93e96 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -8,10 +8,66 @@ package(default_visibility = ["//visibility:public"]) ##### Aggregate libraries ##### -# Full qsim library -# cuda_library +# Full qsim library, minus CUDA cc_library( name = "qsim_lib", + hdrs = [ + "bits.h", + "bitstring.h", + "channel.h", + "channels_cirq.h", + "circuit_noisy.h", + "circuit_qsim_parser.h", + "circuit.h", + "expect.h", + "formux.h", + "fuser.h", + "fuser_basic.h", + "fuser_mqubit.h", + "gate.h", + "gate_appl.h", + "gates_cirq.h", + "gates_qsim.h", + "hybrid.h", + "io_file.h", + "io.h", + "matrix.h", + "mps_simulator.h", + "mps_statespace.h", + "parfor.h", + "qtrajectory.h", + "run_qsim.h", + "run_qsimh.h", + "seqfor.h", + "simmux.h", + "simulator_avx.h", + "simulator_avx512.h", + "simulator_basic.h", + "simulator_sse.h", + "statespace_avx.h", + "statespace_avx512.h", + "statespace_basic.h", + "statespace_sse.h", + "statespace.h", + "umux.h", + "unitaryspace.h", + "unitaryspace_avx.h", + "unitaryspace_avx512.h", + "unitaryspace_basic.h", + "unitaryspace_sse.h", + "unitary_calculator_avx.h", + "unitary_calculator_avx512.h", + "unitary_calculator_basic.h", + "unitary_calculator_sse.h", + "util.h", + "vectorspace.h", + ], +) + +# Full qsim library, including CUDA +# cuda_library +cc_library( + name = "qsim_cuda_lib", hdrs = [ "bits.h", "bitstring.h", From e6ef4cae27bf9c1ee767ef88cb2cb51cfd2ea030 Mon Sep 17 00:00:00 2001 From: J Ross Thomson <39315853+jrossthomson@users.noreply.github.com> Date: Fri, 20 Aug 2021 11:06:30 -0400 Subject: [PATCH 072/246] Update README.md --- docs/tutorials/multinode/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index 28a81271..1535b51e 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -19,7 +19,7 @@ When you have a project created, move on to the next step. ## Configure your enviroment -In your project using the Cloud Console(https://console.google.com/home/dashboard?cloudshell=true), clone this Github repo. +In your project using the Cloud Shell (https://console.google.com/home/dashboard?cloudshell=true), clone this Github repo. ``` git clone https://github.com/jrossthomson/qsim.git ``` From e381bc1e4cd387064047a8731dad822678323d87 Mon Sep 17 00:00:00 2001 From: J Ross Thomson <39315853+jrossthomson@users.noreply.github.com> Date: Fri, 20 Aug 2021 11:07:04 -0400 Subject: [PATCH 073/246] Update README.md --- docs/tutorials/multinode/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index 1535b51e..d33e446a 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -19,7 +19,7 @@ When you have a project created, move on to the next step. ## Configure your enviroment -In your project using the Cloud Shell (https://console.google.com/home/dashboard?cloudshell=true), clone this Github repo. +In your project using the Cloud Shell (https://console.cloud.google.com/home/dashboard?cloudshell=true), clone this Github repo. ``` git clone https://github.com/jrossthomson/qsim.git ``` From b70a82ca7669b9c147d84e9bda7abaea84d6bfc0 Mon Sep 17 00:00:00 2001 From: J Ross Thomson <39315853+jrossthomson@users.noreply.github.com> Date: Fri, 20 Aug 2021 11:12:48 -0400 Subject: [PATCH 074/246] Update README.md Wanted to add Cloud Shelll link with target=blank, but this does not work. --- docs/tutorials/multinode/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index d33e446a..b8d7252b 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -19,7 +19,8 @@ When you have a project created, move on to the next step. ## Configure your enviroment -In your project using the Cloud Shell (https://console.cloud.google.com/home/dashboard?cloudshell=true), clone this Github repo. +In your project using the +[Cloud Shell](https://console.cloud.google.com/home/dashboard?cloudshell=true), clone this Github repo. ``` git clone https://github.com/jrossthomson/qsim.git ``` From 0aab96ab16054bb51663a35db9f9eba0357b9f34 Mon Sep 17 00:00:00 2001 From: J Ross Thomson <39315853+jrossthomson@users.noreply.github.com> Date: Fri, 20 Aug 2021 11:13:30 -0400 Subject: [PATCH 075/246] Update README.md --- docs/tutorials/multinode/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index b8d7252b..d7760938 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -31,7 +31,7 @@ cd qsim/docs/tutorials/multinode/terraform ``` This is where you will use terraform to create the HTCondor cluster required to run your jobs. -### Edit init file to setup environment +### Edit init.sh file to setup environment You can now edit `init.sh` to change the name of the project you are using. You can also change the zone and region as you see fit. More information is available [here](https://cloud.google.com/compute/docs/regions-zones). From 615ee603f3aade3cbbd24c2568812f3fdae37672 Mon Sep 17 00:00:00 2001 From: J Ross Thomson <39315853+jrossthomson@users.noreply.github.com> Date: Fri, 20 Aug 2021 16:30:04 -0400 Subject: [PATCH 076/246] Update README.md --- docs/tutorials/multinode/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index d7760938..561b1746 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -161,7 +161,7 @@ The output will look as follows: Submitting job(s).................................................. 50 job(s) submitted to cluster 2. ``` -If this is the second _submit_ you have run, you can see the output of the all the simualtions. The output will be in the `out` directory. +If this is the second _submit_ you have run, you can see the output of the all the simulations. The output will be in the `out` directory. ``` cat out/out.2-* ``` From 2fb475294e513be2e7e0cdab5685b1064ff60265 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Mon, 23 Aug 2021 19:39:32 +0200 Subject: [PATCH 077/246] Set flush-to-zero and denormals-are-zeros flags. --- apps/qsim_amplitudes.cc | 12 +++++++-- apps/qsim_base.cc | 20 +++++++++++---- apps/qsim_von_neumann.cc | 20 +++++++++++---- apps/qsimh_amplitudes.cc | 12 +++++++-- apps/qsimh_base.cc | 12 +++++++-- docs/cirq_interface.md | 12 +++++++++ docs/usage.md | 22 ++++++++++------- lib/util.h | 21 ++++++++++++++++ pybind_interface/pybind_main.cpp | 42 ++++++++++++++++++++++++++++++++ qsimcirq/qsim_simulator.py | 5 ++++ qsimcirq_tests/qsimcirq_test.py | 14 +++++++++-- tests/BUILD | 1 + tests/simulator_testfixture.h | 41 ++++++++++++++++++++++++++++--- 13 files changed, 203 insertions(+), 31 deletions(-) diff --git a/apps/qsim_amplitudes.cc b/apps/qsim_amplitudes.cc index 29268633..01d50b96 100644 --- a/apps/qsim_amplitudes.cc +++ b/apps/qsim_amplitudes.cc @@ -34,7 +34,7 @@ constexpr char usage[] = "usage:\n ./qsim_amplitudes -c circuit_file " "-d times_to_save_results -i input_files " "-o output_files -s seed -t num_threads " - "-f max_fused_size -v verbosity\n"; + "-f max_fused_size -v verbosity -z\n"; struct Options { std::string circuit_file; @@ -45,6 +45,7 @@ struct Options { unsigned num_threads = 1; unsigned max_fused_size = 2; unsigned verbosity = 0; + bool denormals_are_zeros = false; }; Options GetOptions(int argc, char* argv[]) { @@ -56,7 +57,7 @@ Options GetOptions(int argc, char* argv[]) { return std::atoi(word.c_str()); }; - while ((k = getopt(argc, argv, "c:d:i:s:o:t:f:v:")) != -1) { + while ((k = getopt(argc, argv, "c:d:i:s:o:t:f:v:z")) != -1) { switch (k) { case 'c': opt.circuit_file = optarg; @@ -82,6 +83,9 @@ Options GetOptions(int argc, char* argv[]) { case 'v': opt.verbosity = std::atoi(optarg); break; + case 'z': + opt.denormals_are_zeros = true; + break; default: qsim::IO::errorf(usage); exit(1); @@ -162,6 +166,10 @@ int main(int argc, char* argv[]) { return 1; } + if (opt.denormals_are_zeros) { + SetFlushToZeroAndDenormalsAreZeros(); + } + struct Factory { Factory(unsigned num_threads) : num_threads(num_threads) {} diff --git a/apps/qsim_base.cc b/apps/qsim_base.cc index 062dfdde..8d365e9a 100644 --- a/apps/qsim_base.cc +++ b/apps/qsim_base.cc @@ -26,6 +26,11 @@ #include "../lib/io_file.h" #include "../lib/run_qsim.h" #include "../lib/simmux.h" +#include "../lib/util.h" + +constexpr char usage[] = "usage:\n ./qsim_base -c circuit -d maxtime " + "-s seed -t threads -f max_fused_size " + "-v verbosity -z\n"; struct Options { std::string circuit_file; @@ -34,18 +39,15 @@ struct Options { unsigned num_threads = 1; unsigned max_fused_size = 2; unsigned verbosity = 0; + bool denormals_are_zeros = false; }; Options GetOptions(int argc, char* argv[]) { - constexpr char usage[] = "usage:\n ./qsim_base -c circuit -d maxtime " - "-s seed -t threads -f max_fused_size " - "-v verbosity\n"; - Options opt; int k; - while ((k = getopt(argc, argv, "c:d:s:t:f:v:")) != -1) { + while ((k = getopt(argc, argv, "c:d:s:t:f:v:z")) != -1) { switch (k) { case 'c': opt.circuit_file = optarg; @@ -65,6 +67,9 @@ Options GetOptions(int argc, char* argv[]) { case 'v': opt.verbosity = std::atoi(optarg); break; + case 'z': + opt.denormals_are_zeros = true; + break; default: qsim::IO::errorf(usage); exit(1); @@ -77,6 +82,7 @@ Options GetOptions(int argc, char* argv[]) { bool ValidateOptions(const Options& opt) { if (opt.circuit_file.empty()) { qsim::IO::errorf("circuit file is not provided.\n"); + qsim::IO::errorf(usage); return false; } @@ -114,6 +120,10 @@ int main(int argc, char* argv[]) { return 1; } + if (opt.denormals_are_zeros) { + SetFlushToZeroAndDenormalsAreZeros(); + } + struct Factory { Factory(unsigned num_threads) : num_threads(num_threads) {} diff --git a/apps/qsim_von_neumann.cc b/apps/qsim_von_neumann.cc index df9b32fb..c2264909 100644 --- a/apps/qsim_von_neumann.cc +++ b/apps/qsim_von_neumann.cc @@ -28,6 +28,11 @@ #include "../lib/io_file.h" #include "../lib/run_qsim.h" #include "../lib/simmux.h" +#include "../lib/util.h" + +constexpr char usage[] = "usage:\n ./qsim_von_neumann -c circuit -d maxtime " + "-s seed -t threads -f max_fused_size " + "-v verbosity -z\n"; struct Options { std::string circuit_file; @@ -36,18 +41,15 @@ struct Options { unsigned num_threads = 1; unsigned max_fused_size = 2; unsigned verbosity = 0; + bool denormals_are_zeros = false; }; Options GetOptions(int argc, char* argv[]) { - constexpr char usage[] = "usage:\n ./qsim_von_neumann -c circuit -d maxtime " - "-s seed -t threads -f max_fused_size " - "-v verbosity\n"; - Options opt; int k; - while ((k = getopt(argc, argv, "c:d:s:t:f:v:")) != -1) { + while ((k = getopt(argc, argv, "c:d:s:t:f:v:z")) != -1) { switch (k) { case 'c': opt.circuit_file = optarg; @@ -67,6 +69,9 @@ Options GetOptions(int argc, char* argv[]) { case 'v': opt.verbosity = std::atoi(optarg); break; + case 'z': + opt.denormals_are_zeros = true; + break; default: qsim::IO::errorf(usage); exit(1); @@ -79,6 +84,7 @@ Options GetOptions(int argc, char* argv[]) { bool ValidateOptions(const Options& opt) { if (opt.circuit_file.empty()) { qsim::IO::errorf("circuit file is not provided.\n"); + qsim::IO::errorf(usage); return false; } @@ -99,6 +105,10 @@ int main(int argc, char* argv[]) { return 1; } + if (opt.denormals_are_zeros) { + SetFlushToZeroAndDenormalsAreZeros(); + } + struct Factory { Factory(unsigned num_threads) : num_threads(num_threads) {} diff --git a/apps/qsimh_amplitudes.cc b/apps/qsimh_amplitudes.cc index cf57f121..cfb5b09a 100644 --- a/apps/qsimh_amplitudes.cc +++ b/apps/qsimh_amplitudes.cc @@ -35,7 +35,7 @@ constexpr char usage[] = "usage:\n ./qsimh_amplitudes -c circuit_file " "-d maxtime -k part1_qubits " "-w prefix -p num_prefix_gates -r num_root_gates " "-i input_file -o output_file -t num_threads " - "-v verbosity\n"; + "-v verbosity -z\n"; struct Options { std::string circuit_file; @@ -48,6 +48,7 @@ struct Options { unsigned num_root_gatexs = 0; unsigned num_threads = 1; unsigned verbosity = 0; + bool denormals_are_zeros = false; }; Options GetOptions(int argc, char* argv[]) { @@ -59,7 +60,7 @@ Options GetOptions(int argc, char* argv[]) { return std::atoi(word.c_str()); }; - while ((k = getopt(argc, argv, "c:d:k:w:p:r:i:o:t:v:")) != -1) { + while ((k = getopt(argc, argv, "c:d:k:w:p:r:i:o:t:v:z")) != -1) { switch (k) { case 'c': opt.circuit_file = optarg; @@ -91,6 +92,9 @@ Options GetOptions(int argc, char* argv[]) { case 'v': opt.verbosity = std::atoi(optarg); break; + case 'z': + opt.denormals_are_zeros = true; + break; default: qsim::IO::errorf(usage); exit(1); @@ -180,6 +184,10 @@ int main(int argc, char* argv[]) { } auto parts = GetParts(circuit.num_qubits, opt.part1); + if (opt.denormals_are_zeros) { + SetFlushToZeroAndDenormalsAreZeros(); + } + std::vector bitstrings; auto num_qubits = circuit.num_qubits; if (!BitstringsFromFile(num_qubits, opt.input_file, bitstrings)) { diff --git a/apps/qsimh_base.cc b/apps/qsimh_base.cc index 7b9190d8..c1656a8b 100644 --- a/apps/qsimh_base.cc +++ b/apps/qsimh_base.cc @@ -34,7 +34,7 @@ constexpr char usage[] = "usage:\n ./qsimh_base -c circuit_file " "-d maximum_time -k part1_qubits " "-w prefix -p num_prefix_gates -r num_root_gates " - "-t num_threads -v verbosity\n"; + "-t num_threads -v verbosity -z\n"; struct Options { std::string circuit_file; @@ -45,6 +45,7 @@ struct Options { unsigned num_root_gatexs = 0; unsigned num_threads = 1; unsigned verbosity = 0; + bool denormals_are_zeros = false; }; Options GetOptions(int argc, char* argv[]) { @@ -56,7 +57,7 @@ Options GetOptions(int argc, char* argv[]) { return std::atoi(word.c_str()); }; - while ((k = getopt(argc, argv, "c:d:k:w:p:r:t:v:")) != -1) { + while ((k = getopt(argc, argv, "c:d:k:w:p:r:t:v:z")) != -1) { switch (k) { case 'c': opt.circuit_file = optarg; @@ -82,6 +83,9 @@ Options GetOptions(int argc, char* argv[]) { case 'v': opt.verbosity = std::atoi(optarg); break; + case 'z': + opt.denormals_are_zeros = true; + break; default: qsim::IO::errorf(usage); exit(1); @@ -142,6 +146,10 @@ int main(int argc, char* argv[]) { } auto parts = GetParts(circuit.num_qubits, opt.part1); + if (opt.denormals_are_zeros) { + SetFlushToZeroAndDenormalsAreZeros(); + } + uint64_t num_bitstrings = std::min(uint64_t{8}, uint64_t{1} << circuit.num_qubits); diff --git a/docs/cirq_interface.md b/docs/cirq_interface.md index 4f5d190a..659ec2db 100644 --- a/docs/cirq_interface.md +++ b/docs/cirq_interface.md @@ -117,6 +117,18 @@ the circuit once for each repetition unless all measurements are terminal. This ensures that nondeterminism from intermediate measurements is properly reflected in the results. +In rare cases when the state vector and the gate matrices have many zero +entries (denormal numbers), a significant performance slowdown can occur. +Set the `denormals_are_zeros` option to `True` to prevent this issue +potentially at the cost of a tiny precision loss: + +``` +# equivalent to {'t': 8, 'v': 0, 'z': True} +qsim_options = qsimcirq.QSimOptions(cpu_threads=8, verbosity=0, denormals_are_zeros=True) +my_sim = qsimcirq.QSimSimulator(qsim_options) +myres = my_sim.simulate(program=my_circuit) +``` + #### QSimhSimulator `QSimhSimulator` uses a hybrid Schrödinger-Feynman simulator. This limits it to diff --git a/docs/usage.md b/docs/usage.md index 7bc5e525..8c30d08e 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -13,7 +13,7 @@ Sample circuits are provided in ## qsim_base usage ``` -./qsim_base.x -c circuit_file -d maxtime -t num_threads -f max_fused_size -v verbosity +./qsim_base.x -c circuit_file -d maxtime -t num_threads -f max_fused_size -v verbosity -z ``` | Flag | Description | @@ -23,6 +23,7 @@ Sample circuits are provided in |`-t num_threads` | number of threads to use| |`-f max_fused_size` | maximum fused gate size| |`-v verbosity` | verbosity level (0,1,>1)| +|`-z | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsim_base computes all the amplitudes and just prints the first eight of them (or a smaller number for 1- or 2-qubit circuits). @@ -35,7 +36,7 @@ Example: ## qsim_von_neumann usage ``` -./qsim_von_neumann.x -c circuit_file -d maxtime -t num_threads -f max_fused_size -v verbosity +./qsim_von_neumann.x -c circuit_file -d maxtime -t num_threads -f max_fused_size -v verbosity -z ``` @@ -46,6 +47,7 @@ Example: |`-t num_threads` | number of threads to use| |`-f max_fused_size` | maximum fused gate size| |`-v verbosity` | verbosity level (0,1,>1)| +|`-z | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsim_von_neumann computes all the amplitudes and calculates the von Neumann entropy. Note that this can be quite slow for large circuits and small thread @@ -64,18 +66,19 @@ Example: -i input_files \ -o output_files \ -f max_fused_size \ - -t num_threads -v verbosity + -t num_threads -v verbosity -z ``` | Flag | Description | |-------|------------| |`-c circuit_file` | circuit file to run| -|`-d times_to_save_results` | comma-separated list of circuit times to save results at| +|`-d times_to_save_results` | comma-separated list of circuit times to save results at| |`-i input_files` | comma-separated list of bitstring input files| |`-o output_files` | comma-separated list of amplitude output files| |`-t num_threads` | number of threads to use| |`-f max_fused_size` | maximum fused gate size| |`-v verbosity` | verbosity level (0,1,>1)| +|`-z | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsim_amplitudes reads input files of bitstrings, computes the corresponding amplitudes at specified times and writes them to output files. @@ -97,20 +100,20 @@ Example: -w prefix \ -p num_prefix_gates \ -r num_root_gates \ - -t num_threads -v verbosity + -t num_threads -v verbosity -z ``` | Flag | Description | |-------|------------| |`-c circuit_file` | circuit file to run| |`-d maxtime` | maximum time | -|`-k part1_qubits` | comma-separated list of qubit indices for part 1 | +|`-k part1_qubits` | comma-separated list of qubit indices for part 1| |`-w prefix`| prefix value | |`-p num_prefix_gates` | number of prefix gates| |`-r num_root_gates` | number of root gates| |`-t num_threads` | number of threads to use| |`-v verbosity` | verbosity level (0,>0)| - +|`-z | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsimh_base just computes and just prints the first eight amplitudes. The hybrid Schrödinger-Feynman method is used. The lattice is split into two parts. @@ -176,14 +179,14 @@ maximum "time". -p num_prefix_gates \ -r num_root_gates \ -i input_file -o output_file \ - -t num_threads -v verbosity + -t num_threads -v verbosity -z ``` | Flag | Description | |-------|------------| |`-c circuit_file` | circuit file to run| |`-d maxtime` | maximum time | -|`-k part1_qubits` | comma-separated list of qubit indices for part 1 | +|`-k part1_qubits` | comma-separated list of qubit indices for part 1| |`-w prefix`| prefix value | |`-p num_prefix_gates` | number of prefix gates| |`-r num_root_gates` | number of root gates| @@ -191,6 +194,7 @@ maximum "time". |`-o output_file` | amplitude output file| |`-t num_threads` | number of threads to use| |`-v verbosity` | verbosity level (0,>0)| +|`-z | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsimh_amplitudes reads the input file of bitstrings, computes the corresponding amplitudes and writes them to the output file. The hybrid Schrödinger-Feynman diff --git a/lib/util.h b/lib/util.h index 726a0192..19fbb9fd 100644 --- a/lib/util.h +++ b/lib/util.h @@ -15,6 +15,10 @@ #ifndef UTIL_H_ #define UTIL_H_ +#ifdef __SSE__ +# include +#endif + #include #include #include @@ -84,6 +88,23 @@ inline std::vector GenerateRandomValues( return rs; } +// This function sets flush-to-zero and denormals-are-zeros MXCSR control +// flags. This prevents rare cases of performance slowdown potentially at +// the cost of a tiny precision loss. +void SetFlushToZeroAndDenormalsAreZeros() { +#ifdef __SSE2__ + _mm_setcsr(_mm_getcsr() | 0x8040); +#endif +} + +// This function clears flush-to-zero and denormals-are-zeros MXCSR control +// flags. +void ClearFlushToZeroAndDenormalsAreZeros() { +#ifdef __SSE2__ + _mm_setcsr(_mm_getcsr() & ~unsigned{0x8040}); +#endif +} + } // namespace qsim #endif // UTIL_H_ diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 95c09f77..67a74cae 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -383,12 +383,14 @@ std::vector> qsim_simulate(const py::dict &options) { Factory>; bool use_gpu; + bool denormals_are_zeros; unsigned num_sim_threads; unsigned num_state_threads = 0; unsigned num_dblocks = 0; Runner::Parameter param; try { use_gpu = parseOptions(options, "g\0"); + denormals_are_zeros = parseOptions(options, "z\0"); if (use_gpu) { num_sim_threads = parseOptions(options, "gsmt\0"); num_state_threads = parseOptions(options, "gsst\0"); @@ -403,6 +405,13 @@ std::vector> qsim_simulate(const py::dict &options) { IO::errorf(exp.what()); return {}; } + + if (denormals_are_zeros) { + SetFlushToZeroAndDenormalsAreZeros(); + } else { + ClearFlushToZeroAndDenormalsAreZeros(); + } + Runner::Run( param, Factory(num_sim_threads, num_state_threads, num_dblocks), circuit, measure); @@ -436,6 +445,7 @@ std::vector> qtrajectory_simulate(const py::dict &options) { Runner::Parameter param; bool use_gpu; + bool denormals_are_zeros; unsigned num_sim_threads; unsigned num_state_threads = 0; unsigned num_dblocks = 0; @@ -443,6 +453,7 @@ std::vector> qtrajectory_simulate(const py::dict &options) { try { use_gpu = parseOptions(options, "g\0"); + denormals_are_zeros = parseOptions(options, "z\0"); if (use_gpu) { num_sim_threads = parseOptions(options, "gsmt\0"); num_state_threads = parseOptions(options, "gsst\0"); @@ -470,6 +481,12 @@ std::vector> qtrajectory_simulate(const py::dict &options) { } }; + if (denormals_are_zeros) { + SetFlushToZeroAndDenormalsAreZeros(); + } else { + ClearFlushToZeroAndDenormalsAreZeros(); + } + if (!Runner::RunBatch(param, ncircuit, seed, seed + 1, state_space, simulator, measure)) { IO::errorf("qtrajectory simulation of the circuit errored out.\n"); @@ -544,6 +561,7 @@ class SimulatorHelper { : state_space(Factory(1, 1, 1).CreateStateSpace()), state(StateSpace::Null()), scratch(StateSpace::Null()) { + bool denormals_are_zeros; is_valid = false; is_noisy = noisy; try { @@ -557,6 +575,7 @@ class SimulatorHelper { } use_gpu = parseOptions(options, "g\0"); + denormals_are_zeros = parseOptions(options, "z\0"); if (use_gpu) { num_sim_threads = parseOptions(options, "gsmt\0"); num_state_threads = parseOptions(options, "gsst\0"); @@ -572,6 +591,12 @@ class SimulatorHelper { num_sim_threads, num_state_threads, num_dblocks).CreateStateSpace(); state = state_space.Create(num_qubits); is_valid = true; + + if (denormals_are_zeros) { + SetFlushToZeroAndDenormalsAreZeros(); + } else { + ClearFlushToZeroAndDenormalsAreZeros(); + } } catch (const std::invalid_argument &exp) { // If this triggers, is_valid is false. IO::errorf(exp.what()); @@ -609,6 +634,7 @@ class SimulatorHelper { bool result = false; Factory factory(num_sim_threads, num_state_threads, num_dblocks); + if (is_noisy) { std::vector stat; auto params = get_noisy_params(); @@ -772,12 +798,14 @@ std::vector qsim_sample(const py::dict &options) { Factory>; bool use_gpu; + bool denormals_are_zeros; unsigned num_sim_threads; unsigned num_state_threads = 0; unsigned num_dblocks = 0; Runner::Parameter param; try { use_gpu = parseOptions(options, "g\0"); + denormals_are_zeros = parseOptions(options, "z\0"); if (use_gpu) { num_sim_threads = parseOptions(options, "gsmt\0"); num_state_threads = parseOptions(options, "gsst\0"); @@ -799,6 +827,12 @@ std::vector qsim_sample(const py::dict &options) { State state = state_space.Create(circuit.num_qubits); state_space.SetStateZero(state); + if (denormals_are_zeros) { + SetFlushToZeroAndDenormalsAreZeros(); + } else { + ClearFlushToZeroAndDenormalsAreZeros(); + } + if (!Runner::Run(param, factory, circuit, state, results)) { IO::errorf("qsim sampling of the circuit errored out.\n"); return {}; @@ -830,6 +864,7 @@ std::vector qtrajectory_sample(const py::dict &options) { Runner::Parameter param; bool use_gpu; + bool denormals_are_zeros; unsigned num_sim_threads; unsigned num_state_threads = 0; unsigned num_dblocks = 0; @@ -837,6 +872,7 @@ std::vector qtrajectory_sample(const py::dict &options) { try { use_gpu = parseOptions(options, "g\0"); + denormals_are_zeros = parseOptions(options, "z\0"); if (use_gpu) { num_sim_threads = parseOptions(options, "gsmt\0"); num_state_threads = parseOptions(options, "gsst\0"); @@ -883,6 +919,12 @@ std::vector qtrajectory_sample(const py::dict &options) { } }; + if (denormals_are_zeros) { + SetFlushToZeroAndDenormalsAreZeros(); + } else { + ClearFlushToZeroAndDenormalsAreZeros(); + } + if (!Runner::RunBatch(param, ncircuit, seed, seed + 1, state_space, simulator, measure)) { IO::errorf("qtrajectory sampling of the circuit errored out.\n"); diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index f079f55e..a7b52335 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -142,6 +142,9 @@ class QSimOptions: gpu_data_blocks: number of data blocks to use on GPU. Below 16 data blocks, performance is noticeably reduced. verbosity: Logging verbosity. + denormals_are_zeros: if true, set flush-to-zero and denormals-are-zeros + MXCSR control flags. This prevents rare cases of performance + slowdown potentially at the cost of a tiny precision loss. """ max_fused_gate_size: int = 2 @@ -152,6 +155,7 @@ class QSimOptions: gpu_state_threads: int = 512 gpu_data_blocks: int = 16 verbosity: int = 0 + denormals_are_zeros: bool = False def as_dict(self): """Generates an options dict from this object. @@ -167,6 +171,7 @@ def as_dict(self): "gsst": self.gpu_state_threads, "gdb": self.gpu_data_blocks, "v": self.verbosity, + "z": self.denormals_are_zeros, } diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index e11de449..2d627033 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -1485,12 +1485,22 @@ def test_cirq_qsim_global_shift(): cirq_result = simulator.simulate(circuit) qsim_simulator = qsimcirq.QSimSimulator() - qsim_result = qsim_simulator.simulate(circuit) + qsim_result1 = qsim_simulator.simulate(circuit) assert cirq.linalg.allclose_up_to_global_phase( - qsim_result.state_vector(), cirq_result.state_vector() + qsim_result1.state_vector(), cirq_result.state_vector() ) + qsim_simulator.qsim_options["u"] = True + qsim_result2 = qsim_simulator.simulate(circuit) + + assert (qsim_result1.state_vector() == qsim_result2.state_vector()).all() + + qsim_simulator.qsim_options["u"] = False + qsim_result3 = qsim_simulator.simulate(circuit) + + assert (qsim_result1.state_vector() == qsim_result3.state_vector()).all() + @pytest.mark.parametrize("mode", ["noiseless", "noisy"]) def test_cirq_qsim_circuit_memoization_compute_amplitudes(mode: str): diff --git a/tests/BUILD b/tests/BUILD index 71a4f916..53f72ab4 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -265,6 +265,7 @@ cc_library( "//lib:gate_appl", "//lib:gates_qsim", "//lib:io", + "//lib:util", ], testonly = 1, ) diff --git a/tests/simulator_testfixture.h b/tests/simulator_testfixture.h index ba575f4a..17b571ec 100644 --- a/tests/simulator_testfixture.h +++ b/tests/simulator_testfixture.h @@ -27,6 +27,7 @@ #include "../lib/gate_appl.h" #include "../lib/gates_qsim.h" #include "../lib/io.h" +#include "../lib/util.h" namespace qsim { @@ -460,11 +461,11 @@ void TestCircuitWithControlledGates(const Factory& factory) { StateSpace state_space = factory.CreateStateSpace(); Simulator simulator = factory.CreateSimulator(); - auto state = state_space.Create(num_qubits); - state_space.SetStateZero(state); + auto state1 = state_space.Create(num_qubits); + state_space.SetStateZero(state1); for (const auto& gate : gates) { - ApplyGate(simulator, gate, state); + ApplyGate(simulator, gate, state1); } /* @@ -717,10 +718,42 @@ if __name__ == '__main__': unsigned size = 1 << num_qubits; for (unsigned i = 0; i < size; ++i) { - auto a = StateSpace::GetAmpl(state, i); + auto a = StateSpace::GetAmpl(state1, i); EXPECT_NEAR(std::real(a), expected_results[i][0], 1e-6); EXPECT_NEAR(std::imag(a), expected_results[i][1], 1e-6); } + + SetFlushToZeroAndDenormalsAreZeros(); + + auto state2 = state_space.Create(num_qubits); + state_space.SetStateZero(state2); + + for (const auto& gate : gates) { + ApplyGate(simulator, gate, state2); + } + + for (unsigned i = 0; i < size; ++i) { + auto a1 = StateSpace::GetAmpl(state1, i); + auto a2 = StateSpace::GetAmpl(state2, i); + EXPECT_EQ(std::real(a1), std::real(a2)); + EXPECT_EQ(std::imag(a1), std::imag(a2)); + } + + ClearFlushToZeroAndDenormalsAreZeros(); + + auto state3 = state_space.Create(num_qubits); + state_space.SetStateZero(state3); + + for (const auto& gate : gates) { + ApplyGate(simulator, gate, state3); + } + + for (unsigned i = 0; i < size; ++i) { + auto a1 = StateSpace::GetAmpl(state1, i); + auto a2 = StateSpace::GetAmpl(state3, i); + EXPECT_EQ(std::real(a1), std::real(a2)); + EXPECT_EQ(std::imag(a1), std::imag(a2)); + } } template From df527863af059e082dab3c8ba39ae779d32c71ec Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Mon, 23 Aug 2021 19:50:01 +0200 Subject: [PATCH 078/246] Fix typos. --- docs/cirq_interface.md | 8 ++++---- docs/usage.md | 10 +++++----- qsimcirq_tests/qsimcirq_test.py | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/cirq_interface.md b/docs/cirq_interface.md index 659ec2db..5184ed3d 100644 --- a/docs/cirq_interface.md +++ b/docs/cirq_interface.md @@ -117,10 +117,10 @@ the circuit once for each repetition unless all measurements are terminal. This ensures that nondeterminism from intermediate measurements is properly reflected in the results. -In rare cases when the state vector and the gate matrices have many zero -entries (denormal numbers), a significant performance slowdown can occur. -Set the `denormals_are_zeros` option to `True` to prevent this issue -potentially at the cost of a tiny precision loss: +In rare cases when the state vector and gate matrices have many zero entries +(denormal numbers), a significant performance slowdown can occur. Set +the `denormals_are_zeros` option to `True` to prevent this issue potentially +at the cost of a tiny precision loss: ``` # equivalent to {'t': 8, 'v': 0, 'z': True} diff --git a/docs/usage.md b/docs/usage.md index 8c30d08e..06b8712e 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -23,7 +23,7 @@ Sample circuits are provided in |`-t num_threads` | number of threads to use| |`-f max_fused_size` | maximum fused gate size| |`-v verbosity` | verbosity level (0,1,>1)| -|`-z | set flush-to-zero and denormals-are-zeros MXCSR control flags| +|`-z` | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsim_base computes all the amplitudes and just prints the first eight of them (or a smaller number for 1- or 2-qubit circuits). @@ -47,7 +47,7 @@ Example: |`-t num_threads` | number of threads to use| |`-f max_fused_size` | maximum fused gate size| |`-v verbosity` | verbosity level (0,1,>1)| -|`-z | set flush-to-zero and denormals-are-zeros MXCSR control flags| +|`-z` | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsim_von_neumann computes all the amplitudes and calculates the von Neumann entropy. Note that this can be quite slow for large circuits and small thread @@ -78,7 +78,7 @@ Example: |`-t num_threads` | number of threads to use| |`-f max_fused_size` | maximum fused gate size| |`-v verbosity` | verbosity level (0,1,>1)| -|`-z | set flush-to-zero and denormals-are-zeros MXCSR control flags| +|`-z` | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsim_amplitudes reads input files of bitstrings, computes the corresponding amplitudes at specified times and writes them to output files. @@ -113,7 +113,7 @@ Example: |`-r num_root_gates` | number of root gates| |`-t num_threads` | number of threads to use| |`-v verbosity` | verbosity level (0,>0)| -|`-z | set flush-to-zero and denormals-are-zeros MXCSR control flags| +|`-z` | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsimh_base just computes and just prints the first eight amplitudes. The hybrid Schrödinger-Feynman method is used. The lattice is split into two parts. @@ -194,7 +194,7 @@ maximum "time". |`-o output_file` | amplitude output file| |`-t num_threads` | number of threads to use| |`-v verbosity` | verbosity level (0,>0)| -|`-z | set flush-to-zero and denormals-are-zeros MXCSR control flags| +|`-z` | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsimh_amplitudes reads the input file of bitstrings, computes the corresponding amplitudes and writes them to the output file. The hybrid Schrödinger-Feynman diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 2d627033..b6acd09a 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -1491,12 +1491,12 @@ def test_cirq_qsim_global_shift(): qsim_result1.state_vector(), cirq_result.state_vector() ) - qsim_simulator.qsim_options["u"] = True + qsim_simulator.qsim_options["z"] = True qsim_result2 = qsim_simulator.simulate(circuit) assert (qsim_result1.state_vector() == qsim_result2.state_vector()).all() - qsim_simulator.qsim_options["u"] = False + qsim_simulator.qsim_options["z"] = False qsim_result3 = qsim_simulator.simulate(circuit) assert (qsim_result1.state_vector() == qsim_result3.state_vector()).all() From 7f51ae14c77897a92dae0643c2146296b5b7581c Mon Sep 17 00:00:00 2001 From: Laurynas Tamulevicius Date: Tue, 24 Aug 2021 09:44:03 +0100 Subject: [PATCH 079/246] Update install_qsimcirq.md Removed Python 3.5 references --- docs/install_qsimcirq.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/install_qsimcirq.md b/docs/install_qsimcirq.md index 82cfc0d5..9ae35d21 100644 --- a/docs/install_qsimcirq.md +++ b/docs/install_qsimcirq.md @@ -19,19 +19,19 @@ file, and will be automatically installed along with qsimcirq. ## Linux installation -We provide `qsimcirq` Python wheels on 64-bit `x86` architectures with `Python 3.{5,6,7,8,9}`. +We provide `qsimcirq` Python wheels on 64-bit `x86` architectures with `Python 3.{6,7,8,9}`. Simply run `pip3 install qsimcirq`. ## MacOS installation -We provide `qsimcirq` Python wheels on `x86` architectures with `Python 3.{5,6,7,8,9}`. +We provide `qsimcirq` Python wheels on `x86` architectures with `Python 3.{6,7,8,9}`. Simply run `pip3 install qsimcirq`. ## Windows installation -We provide `qsimcirq` Python wheels on 64-bit `x86` and `amd64` architectures with `Python 3.{5,6,7,8,9}`. +We provide `qsimcirq` Python wheels on 64-bit `x86` and `amd64` architectures with `Python 3.{6,7,8,9}`. Simply run `pip3 install qsimcirq`. From f9670323b65e0b0f58f375e52bc5199389b4ad8c Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Mon, 30 Aug 2021 21:02:39 +0000 Subject: [PATCH 080/246] Added more detail on the readme --- docs/tutorials/multinode/terraform/README.md | 120 ++++++++++++++----- docs/tutorials/multinode/terraform/init.sh | 11 +- 2 files changed, 99 insertions(+), 32 deletions(-) diff --git a/docs/tutorials/multinode/terraform/README.md b/docs/tutorials/multinode/terraform/README.md index 5459045a..800e08ef 100644 --- a/docs/tutorials/multinode/terraform/README.md +++ b/docs/tutorials/multinode/terraform/README.md @@ -1,50 +1,88 @@ # Multinode quantum simulation using HTCondor on GCP -This tutorial will take you through the process of running many qsim simulations -on Google Cloud. In some situations, it is required to run many instances of the same -simulation. This could be used to provide a parameter sweep or to evaluation noise characteristics. +This tutorial will take you through the process of running multiple simultaneous +`qsim` simulations on Google Cloud. In some situations, it is required to run +many instances of the same simulation. This could be used to provide a parameter +sweep or to evaluation noise characteristics. + +One of the key competencies of a quantum computing effort is the ability to run +simulations. While quantum computing hardware is still years from general +availability, quantum computing simulation with `qsim` and `Cirq` is available for +researchers exploring a quantum program. It is expected that simulation will be +a gating requirement to make use of physical hardware, to prove out algorithms +both for computability and stability under noisy conditions. Many thousands of +simulation runs will typically be required to prove out the gating requirements +for each circuit, sometimes requiring years of simulation in advance of any +practical use of the quantum hardware. ## Objectives -* Use Terraform to deploy a HTCondor cluster +* Use `terraform` to deploy a HTCondor cluster * Run a multinode simulation using HTCondor * Query cluster information and monitor running jobs in HTCondor +* Use `terraform` to destroy the cluster -## Create a project +## Step 1: Create a project In a seperate tutorial the method to create Google Cloud project is explained in detail. [Please refer to this tutorial for guidance](https://quantumai.google/qsim/tutorials/qsimcirq_gcp#gcp_setup). When you have a project created, move on to the next step. -## Configure your enviroment +## Step 2: Configure your enviroment -In your project using the Cloud Console(https://console.google.com/home/dashboard?cloudshell=true), clone this Github repo. -``` +Although this tutorial can be run from your local computer, we recommend the use +of [Google Cloud Shell](https://cloud.google.com/shell). Cloud Shell has many useful tools pre-installed. + +Once you have created a project in the previous step, +the Cloud Console with Cloud Shell activeated can be reached through this link: (https://console.google.com/home/dashboard?cloudshell=true) + +### Clone this repo + +In your Clous Shell window, clone this Github repo. +``` bash git clone https://github.com/jrossthomson/qsim.git ``` +### Change directory Change directory to the tutorial: -``` +``` bash cd qsim/docs/tutorials/multinode/terraform ``` -This is where you will use terraform to create the HTCondor cluster required to run your jobs. +This is where you will use `terraform` to create the HTCondor cluster required to run your jobs. -### Edit init file to setup environment +### Edit `init.sh` file to customize your environment -You can now edit `init.sh` to change the name of the project you are using. You can also change the zone and region as you see fit. More information is available [here](https://cloud.google.com/compute/docs/regions-zones). +You can now edit `init.sh` to change the name of the project you are using. You +can also change the zone and region as you see fit. More information is +available [here](https://cloud.google.com/compute/docs/regions-zones). -Change the variables to reflect your project, most important is the first line: +Use your favorite text file editor, either the integrated [Cloud Shell +Editor](https://cloud.google.com/shell/docs/editor-overview), `vim`, `emacs` or `nano`. +For example: +``` bash +vim init.sh +``` +The file has many lines, but only edit the first 4. +``` bash +export TF_VAR_project=quantum-htcondor-15 +export TF_VAR_project_id=us-east4-c +export TF_VAR_zone=us-east4-c +export TF_VAR_region=us-east4 ``` + +The most important is the first line, indicating the name of the project you created above. +``` bash export TF_VAR_project=my-quantum-htcondor-project ``` The project id needs to be globally unique and to be the same as the project you just created. +### Source the `init.sh` file The edited `init.sh` file should be "sourced" in the cloud shell: -``` +``` bash source init.sh ``` -Repsond in the affirmative to any pop-ups that request permissions on the Cloud platform. +Respond `Agree` to any pop-ups that request permissions on the Google Cloud platform. The final outcome of this script will include: @@ -53,36 +91,53 @@ The final outcome of this script will include: * The appropriate permissions assigned to the service account * A key file created to enable the use of Google Cloud automation. -This will take about 60 seconds. At the end you will see output about permissions and the configuration of the account. +This will take up to 60 seconds. At the end you will see output about +permissions and the configuration of the account. -## Run terraform +## Step 3: Run terraform -When this is complete, you can initialize teraform to begin your cluster creations: -``` +After the previous steps are completed, you can initialize `terraform` to begin your cluster creation. +The first step is to initialize the `terraform` state. +``` bash terraform init ``` -A correct result will contain: +A successful result will contain the text: ``` Terraform has been successfully initialized! ``` -Some terraform commands are wrapped in a makefile, so you can now create your cluster: -``` +### Run the `make` command +For convenience, some terraform commands are prepared in a `Makefile`. This means +you can now create your cluster, with a simple `make` command. +```bash make apply ``` A successful run will show: ``` Apply complete! Resources: 4 added, 0 changed, 0 destroyed. ``` -## Connect to the Submit node for HTCondor -You will now be able list the VMs created: +## Step4: Connect to the _submit_ node for HTCondor +Although there are ways to run `HTCondor` commands from your local machine, +the normal path is to login to the _submit_ node. From there you can run +commands to submit and monitor jobs on HTCondor. + +### List VMs that were created by + +You can list the VMs created. One of them will be the submit node. It will be the VM with +"submit" in the name. + ``` gcloud compute instances list ``` -You will log in to the `submit` node: -``` +Identify the node name, then log in to the `submit` node. If you used the +standard install, the cluster name is "c". In that case you would connect using +the `gcloud ssh` command. +```bash gcloud compute ssh c-submit ``` -Now you are logged in to your HTCondor cluster. +Now you are logged in to your HTCondor cluster. You will see a command prompt something like +```bash +[mylogin@c-submit ~]$ +``` ### Checking the status You can verify if the HTCondor install is completed: @@ -100,9 +155,15 @@ Total for all users: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, ``` If you get `command not found`, you will need to wait a few minutes for the HTCondor install to complete. -## Clone the repo on your cluster +## Step 5: Get the sample code and run it +The HTCondor cluster is now ready for your jobs to be run. If you are familiar with HTCondor and you want to run your own jobs, you may do so. + +If you don't have jobs to run, you can get sample jobs from this Github repo. You will clone the repo to +the submit node and run a job. + +## Clone the repo on your cluster -Again on the `submit` node you you can install the repo to get access to previously created submission files: +on the `submit` node you you can install the repo to get access to previously created submission files: ``` git clone https://github.com/jrossthomson/qsim.git ``` @@ -110,6 +171,7 @@ Then cd to the tutorial directory. ``` cd qsim/docs/tutorials/multinode ``` +### Submit a job Now it is possible to submit a job: ``` condor_submit circuit_q24.sub diff --git a/docs/tutorials/multinode/terraform/init.sh b/docs/tutorials/multinode/terraform/init.sh index 0d10befe..fdf46d3d 100644 --- a/docs/tutorials/multinode/terraform/init.sh +++ b/docs/tutorials/multinode/terraform/init.sh @@ -1,11 +1,15 @@ +# ---- Edit below -----# + export TF_VAR_project=quantum-htcondor-15 export TF_VAR_project_id=us-east4-c export TF_VAR_zone=us-east4-c export TF_VAR_region=us-east4 + +# ---- Do not edit below -----# + export TF_VAR_project_id=${TF_VAR_project} export TF_VAR_service_account="htcondor@"${TF_VAR_project}".iam.gserviceaccount.com" - gcloud config set project $TF_VAR_project gcloud services enable compute.googleapis.com gcloud services enable monitoring.googleapis.com @@ -14,7 +18,6 @@ gcloud config set compute/region $TF_VAR_region gcloud config list - gcloud iam service-accounts create htcondor --display-name="Run HTCondor" # Add roles @@ -24,6 +27,8 @@ gcloud projects add-iam-policy-binding ${TF_VAR_project} --member serviceAccount gcloud projects add-iam-policy-binding ${TF_VAR_project} --member serviceAccount:${TF_VAR_service_account} --role roles/logging.admin gcloud projects add-iam-policy-binding ${TF_VAR_project} --member serviceAccount:${TF_VAR_service_account} --role roles/autoscaling.metricsWriter +""" # Save key file locally. gcloud iam service-accounts keys create ~/.${TF_VAR_project_id}.json --iam-account=${TF_VAR_service_account} -export GOOGLE_APPLICATION_CREDENTIALS=~/.${TF_VAR_project_id}.json \ No newline at end of file +export GOOGLE_APPLICATION_CREDENTIALS=~/.${TF_VAR_project_id}.json +""" \ No newline at end of file From 7a349a2902cad68d0e57b6fcd49385483319e018 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Mon, 30 Aug 2021 22:07:13 +0000 Subject: [PATCH 081/246] remove local saving key file and expanded readme --- docs/tutorials/multinode/terraform/README.md | 100 ++++++++++++++++--- docs/tutorials/multinode/terraform/init.sh | 6 -- 2 files changed, 88 insertions(+), 18 deletions(-) diff --git a/docs/tutorials/multinode/terraform/README.md b/docs/tutorials/multinode/terraform/README.md index 800e08ef..3d8a34ef 100644 --- a/docs/tutorials/multinode/terraform/README.md +++ b/docs/tutorials/multinode/terraform/README.md @@ -128,13 +128,18 @@ You can list the VMs created. One of them will be the submit node. It will be th ``` gcloud compute instances list ``` -Identify the node name, then log in to the `submit` node. If you used the -standard install, the cluster name is "c". In that case you would connect using -the `gcloud ssh` command. +You will see two instances listed: +* c-manager: the core controller node +* c-submit: the submit node where you will run interactive work. + +Identify then log in to the `submit` node. If you used the standard install, the +cluster name is "c". In that case you would connect using the `gcloud ssh` +command. ```bash gcloud compute ssh c-submit ``` -Now you are logged in to your HTCondor cluster. You will see a command prompt something like +Now you are logged in to your HTCondor cluster. You will see a command prompt +something like: ```bash [mylogin@c-submit ~]$ ``` @@ -181,7 +186,13 @@ If successful, the output will be: Submitting job(s). 1 job(s) submitted to cluster 1. ``` -This may take a few minutes, but when completed the command: +You can see the job in queue by with the `condor_q` command. +``` +condor_q +``` +The job will take several minutes to finish. The time includes creating a VM +compute node, installing the HTCondor system and running the job. When completed +the files will be in the `out` directory. ``` ls out ``` @@ -197,8 +208,28 @@ After the job s completed, the ouput of the job can be seen: ``` cat out/out.1-0 ``` -## Running noisy simulations -To run multiple simulations, you can run the submit file `noise.sub`: +## Step5: Run multi-node noise simulations +Noise simulations make use of a [Monte Carlo +method](https://en.wikipedia.org/wiki/Monte_Carlo_method) for [quantum +trajectories](https://en.wikipedia.org/wiki/Quantum_Trajectory_Theory). + +The primary goal of these simulations is to understand the behavior of the +quantum circuit taking into account the effects of noise. There are many +potential sources of noise in a quantum circuit, including thermal noise, 1/f +noise or shot noise, as well as perturbations from cosmic rays. Understanding +and calibrating noise in quantum computing is an active field of investigation, +and it will require well characterized simulations. + +To run multiple simulations, you can run the submit file `noise.sub`. The +submit file is shown below. It is key to note that this is running a `docker` +container. Also, the `queue 50` command at the end of the file submits 50 +separate instances of the container. Since this job is noise driven, the output +of each simulation with be different. A typical analysis of the output would be +a statistical study of the means and variations, perhaps looking for parameter +ranges where circuits are particularly susceptible to noise. + + +### The noise.sub file ``` universe = docker docker_image = gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest @@ -212,7 +243,7 @@ log = out/log.$(Cluster)-$(Process) request_memory = 10GB queue 50 ``` -The final line in this submit file has `queue 50`. This means 50 instances of this simulation will be run. The job can be submitted with the `condor_submit` command. +The job can be submitted with the `condor_submit` command. ``` condor_submit noise.sub ``` @@ -221,7 +252,21 @@ The output will look as follows: Submitting job(s).................................................. 50 job(s) submitted to cluster 2. ``` -If this is the second _submit_ you have run, you can see the output of the all the simualtions. The output will be in the `out` directory. +To monitor the ongoing process of jobs running, you can take advantage of the +Linux `watch` command and run `condor_q` repeatedly. When complete you watch can +be exited with cntrl-c. +``` +watch "condor_q; condor_status" +``` +The output of this command will show you the jobs in the queue as well as the +VMs being created to run the jobs. There is a limit of 20 VMs for this +configuration of the cluster. This can be modified for future runs. + +When the command shows the queue is empty, the command can be stopped with cntl-c. + +If this is the second _submit_ you have run, you can see the output of the all +the simualtions. The output will be in the `out` directory. + ``` cat out/out.2-* ``` @@ -241,15 +286,46 @@ Counter({3: 466, 0: 442, 2: 51, 1: 41}) ``` Note that because this is a noise driven circuit, the results of each simulation are different. -To run your own simulations, simply create a noisy circuit in your _qsim_ python file. +## Next steps + +To run your own simulations, simply create a noisy circuit in your _qsim_ python file. +The file being run in this was `noise3.py`. You can take this example and expand +to your own circuit. + +```python +import cirq, qsimcirq + +# Create a Bell state, |00) + |11) +q0, q1 = cirq.LineQubit.range(2) +circuit = cirq.Circuit( + cirq.H(q0), + cirq.CNOT(q0, q1), + cirq.measure(q0, q1, key='m') +) + +# Constructs a noise model that adds depolarizing noise after each gate. +noise = cirq.NoiseModel.from_noise_model_like(cirq.depolarize(p=0.05)) + +# Use the noise model to create a noisy circuit. +noisy_circuit = cirq.Circuit(noise.noisy_moments(circuit, system_qubits=[q0, q1])) + +sim = qsimcirq.QSimSimulator() +result = sim.run(noisy_circuit, repetitions=1000) +# Outputs a histogram dict of result:count pairs. +# Expected result is a bunch of 0s and 3s, with fewer 1s and 2s. +# (For comparison, the noiseless circuit will only have 0s and 3s) +print(result.histogram(key='m')) +``` There are many more examples of circuits to be run [here](https://quantumai.google/cirq/noise) -## Shutting down -When you are done with this tutorial, it is important to remove the resources. You can do this with _terraform_ +## FINAL STEP: Shutting down + +> **IMPORTANT**: To avoid excess billing for this project, it is important to shut down the cluster. This is easily done using the make command built for this purpose. ``` make destroy ``` + > The most effective way to ensure you are not charged is to delete the project. [The instructions are here.](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) diff --git a/docs/tutorials/multinode/terraform/init.sh b/docs/tutorials/multinode/terraform/init.sh index fdf46d3d..43a6471b 100644 --- a/docs/tutorials/multinode/terraform/init.sh +++ b/docs/tutorials/multinode/terraform/init.sh @@ -26,9 +26,3 @@ gcloud projects add-iam-policy-binding ${TF_VAR_project} --member serviceAccount gcloud projects add-iam-policy-binding ${TF_VAR_project} --member serviceAccount:${TF_VAR_service_account} --role roles/monitoring.admin gcloud projects add-iam-policy-binding ${TF_VAR_project} --member serviceAccount:${TF_VAR_service_account} --role roles/logging.admin gcloud projects add-iam-policy-binding ${TF_VAR_project} --member serviceAccount:${TF_VAR_service_account} --role roles/autoscaling.metricsWriter - -""" -# Save key file locally. -gcloud iam service-accounts keys create ~/.${TF_VAR_project_id}.json --iam-account=${TF_VAR_service_account} -export GOOGLE_APPLICATION_CREDENTIALS=~/.${TF_VAR_project_id}.json -""" \ No newline at end of file From 17f013b5ce79ef153722798fdfef3afe0a260f64 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Mon, 30 Aug 2021 22:24:38 +0000 Subject: [PATCH 082/246] Syncing both Readmes --- docs/tutorials/multinode/README.md | 214 +++++++++++++++++++++++------ 1 file changed, 175 insertions(+), 39 deletions(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index 561b1746..3d8a34ef 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -1,52 +1,88 @@ - # Multinode quantum simulation using HTCondor on GCP -This tutorial will take you through the process of running many qsim simulations -on Google Cloud. In some situations, it is required to run many instances of the same -simulation. This could be used to provide a parameter sweep or to evaluation noise characteristics. +This tutorial will take you through the process of running multiple simultaneous +`qsim` simulations on Google Cloud. In some situations, it is required to run +many instances of the same simulation. This could be used to provide a parameter +sweep or to evaluation noise characteristics. + +One of the key competencies of a quantum computing effort is the ability to run +simulations. While quantum computing hardware is still years from general +availability, quantum computing simulation with `qsim` and `Cirq` is available for +researchers exploring a quantum program. It is expected that simulation will be +a gating requirement to make use of physical hardware, to prove out algorithms +both for computability and stability under noisy conditions. Many thousands of +simulation runs will typically be required to prove out the gating requirements +for each circuit, sometimes requiring years of simulation in advance of any +practical use of the quantum hardware. ## Objectives -* Use Terraform to deploy a HTCondor cluster +* Use `terraform` to deploy a HTCondor cluster * Run a multinode simulation using HTCondor * Query cluster information and monitor running jobs in HTCondor +* Use `terraform` to destroy the cluster -## Create a project +## Step 1: Create a project In a seperate tutorial the method to create Google Cloud project is explained in detail. [Please refer to this tutorial for guidance](https://quantumai.google/qsim/tutorials/qsimcirq_gcp#gcp_setup). When you have a project created, move on to the next step. -## Configure your enviroment +## Step 2: Configure your enviroment -In your project using the -[Cloud Shell](https://console.cloud.google.com/home/dashboard?cloudshell=true), clone this Github repo. -``` +Although this tutorial can be run from your local computer, we recommend the use +of [Google Cloud Shell](https://cloud.google.com/shell). Cloud Shell has many useful tools pre-installed. + +Once you have created a project in the previous step, +the Cloud Console with Cloud Shell activeated can be reached through this link: (https://console.google.com/home/dashboard?cloudshell=true) + +### Clone this repo + +In your Clous Shell window, clone this Github repo. +``` bash git clone https://github.com/jrossthomson/qsim.git ``` +### Change directory Change directory to the tutorial: -``` +``` bash cd qsim/docs/tutorials/multinode/terraform ``` -This is where you will use terraform to create the HTCondor cluster required to run your jobs. +This is where you will use `terraform` to create the HTCondor cluster required to run your jobs. -### Edit init.sh file to setup environment +### Edit `init.sh` file to customize your environment -You can now edit `init.sh` to change the name of the project you are using. You can also change the zone and region as you see fit. More information is available [here](https://cloud.google.com/compute/docs/regions-zones). +You can now edit `init.sh` to change the name of the project you are using. You +can also change the zone and region as you see fit. More information is +available [here](https://cloud.google.com/compute/docs/regions-zones). -Change the variables to reflect your project, most important is the first line: +Use your favorite text file editor, either the integrated [Cloud Shell +Editor](https://cloud.google.com/shell/docs/editor-overview), `vim`, `emacs` or `nano`. +For example: +``` bash +vim init.sh +``` +The file has many lines, but only edit the first 4. +``` bash +export TF_VAR_project=quantum-htcondor-15 +export TF_VAR_project_id=us-east4-c +export TF_VAR_zone=us-east4-c +export TF_VAR_region=us-east4 ``` + +The most important is the first line, indicating the name of the project you created above. +``` bash export TF_VAR_project=my-quantum-htcondor-project ``` The project id needs to be globally unique and to be the same as the project you just created. +### Source the `init.sh` file The edited `init.sh` file should be "sourced" in the cloud shell: -``` +``` bash source init.sh ``` -Repsond in the affirmative to any pop-ups that request permissions on the Cloud platform. +Respond `Agree` to any pop-ups that request permissions on the Google Cloud platform. The final outcome of this script will include: @@ -55,36 +91,58 @@ The final outcome of this script will include: * The appropriate permissions assigned to the service account * A key file created to enable the use of Google Cloud automation. -This will take about 60 seconds. At the end you will see output about permissions and the configuration of the account. +This will take up to 60 seconds. At the end you will see output about +permissions and the configuration of the account. -## Run terraform +## Step 3: Run terraform -When this is complete, you can initialize teraform to begin your cluster creations: -``` +After the previous steps are completed, you can initialize `terraform` to begin your cluster creation. +The first step is to initialize the `terraform` state. +``` bash terraform init ``` -A correct result will contain: +A successful result will contain the text: ``` Terraform has been successfully initialized! ``` -Some terraform commands are wrapped in a makefile, so you can now create your cluster: -``` +### Run the `make` command +For convenience, some terraform commands are prepared in a `Makefile`. This means +you can now create your cluster, with a simple `make` command. +```bash make apply ``` A successful run will show: ``` Apply complete! Resources: 4 added, 0 changed, 0 destroyed. ``` -## Connect to the Submit node for HTCondor -You will now be able list the VMs created: +## Step4: Connect to the _submit_ node for HTCondor +Although there are ways to run `HTCondor` commands from your local machine, +the normal path is to login to the _submit_ node. From there you can run +commands to submit and monitor jobs on HTCondor. + +### List VMs that were created by + +You can list the VMs created. One of them will be the submit node. It will be the VM with +"submit" in the name. + ``` gcloud compute instances list ``` -You will log in to the `submit` node: -``` +You will see two instances listed: +* c-manager: the core controller node +* c-submit: the submit node where you will run interactive work. + +Identify then log in to the `submit` node. If you used the standard install, the +cluster name is "c". In that case you would connect using the `gcloud ssh` +command. +```bash gcloud compute ssh c-submit ``` -Now you are logged in to your HTCondor cluster. +Now you are logged in to your HTCondor cluster. You will see a command prompt +something like: +```bash +[mylogin@c-submit ~]$ +``` ### Checking the status You can verify if the HTCondor install is completed: @@ -102,9 +160,15 @@ Total for all users: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, ``` If you get `command not found`, you will need to wait a few minutes for the HTCondor install to complete. -## Clone the repo on your cluster +## Step 5: Get the sample code and run it +The HTCondor cluster is now ready for your jobs to be run. If you are familiar with HTCondor and you want to run your own jobs, you may do so. + +If you don't have jobs to run, you can get sample jobs from this Github repo. You will clone the repo to +the submit node and run a job. -Again on the `submit` node you you can install the repo to get access to previously created submission files: +## Clone the repo on your cluster + +on the `submit` node you you can install the repo to get access to previously created submission files: ``` git clone https://github.com/jrossthomson/qsim.git ``` @@ -112,6 +176,7 @@ Then cd to the tutorial directory. ``` cd qsim/docs/tutorials/multinode ``` +### Submit a job Now it is possible to submit a job: ``` condor_submit circuit_q24.sub @@ -121,7 +186,13 @@ If successful, the output will be: Submitting job(s). 1 job(s) submitted to cluster 1. ``` -This may take a few minutes, but when completed the command: +You can see the job in queue by with the `condor_q` command. +``` +condor_q +``` +The job will take several minutes to finish. The time includes creating a VM +compute node, installing the HTCondor system and running the job. When completed +the files will be in the `out` directory. ``` ls out ``` @@ -137,8 +208,28 @@ After the job s completed, the ouput of the job can be seen: ``` cat out/out.1-0 ``` -## Running noisy simulations -To run multiple simulations, you can run the submit file `noise.sub`: +## Step5: Run multi-node noise simulations +Noise simulations make use of a [Monte Carlo +method](https://en.wikipedia.org/wiki/Monte_Carlo_method) for [quantum +trajectories](https://en.wikipedia.org/wiki/Quantum_Trajectory_Theory). + +The primary goal of these simulations is to understand the behavior of the +quantum circuit taking into account the effects of noise. There are many +potential sources of noise in a quantum circuit, including thermal noise, 1/f +noise or shot noise, as well as perturbations from cosmic rays. Understanding +and calibrating noise in quantum computing is an active field of investigation, +and it will require well characterized simulations. + +To run multiple simulations, you can run the submit file `noise.sub`. The +submit file is shown below. It is key to note that this is running a `docker` +container. Also, the `queue 50` command at the end of the file submits 50 +separate instances of the container. Since this job is noise driven, the output +of each simulation with be different. A typical analysis of the output would be +a statistical study of the means and variations, perhaps looking for parameter +ranges where circuits are particularly susceptible to noise. + + +### The noise.sub file ``` universe = docker docker_image = gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest @@ -152,7 +243,7 @@ log = out/log.$(Cluster)-$(Process) request_memory = 10GB queue 50 ``` -The final line in this submit file has `queue 50`. This means 50 instances of this simulation will be run. The job can be submitted with the `condor_submit` command. +The job can be submitted with the `condor_submit` command. ``` condor_submit noise.sub ``` @@ -161,7 +252,21 @@ The output will look as follows: Submitting job(s).................................................. 50 job(s) submitted to cluster 2. ``` -If this is the second _submit_ you have run, you can see the output of the all the simulations. The output will be in the `out` directory. +To monitor the ongoing process of jobs running, you can take advantage of the +Linux `watch` command and run `condor_q` repeatedly. When complete you watch can +be exited with cntrl-c. +``` +watch "condor_q; condor_status" +``` +The output of this command will show you the jobs in the queue as well as the +VMs being created to run the jobs. There is a limit of 20 VMs for this +configuration of the cluster. This can be modified for future runs. + +When the command shows the queue is empty, the command can be stopped with cntl-c. + +If this is the second _submit_ you have run, you can see the output of the all +the simualtions. The output will be in the `out` directory. + ``` cat out/out.2-* ``` @@ -181,15 +286,46 @@ Counter({3: 466, 0: 442, 2: 51, 1: 41}) ``` Note that because this is a noise driven circuit, the results of each simulation are different. -To run your own simulations, simply create a noisy circuit in your _qsim_ python file. +## Next steps + +To run your own simulations, simply create a noisy circuit in your _qsim_ python file. +The file being run in this was `noise3.py`. You can take this example and expand +to your own circuit. + +```python +import cirq, qsimcirq + +# Create a Bell state, |00) + |11) +q0, q1 = cirq.LineQubit.range(2) +circuit = cirq.Circuit( + cirq.H(q0), + cirq.CNOT(q0, q1), + cirq.measure(q0, q1, key='m') +) + +# Constructs a noise model that adds depolarizing noise after each gate. +noise = cirq.NoiseModel.from_noise_model_like(cirq.depolarize(p=0.05)) + +# Use the noise model to create a noisy circuit. +noisy_circuit = cirq.Circuit(noise.noisy_moments(circuit, system_qubits=[q0, q1])) + +sim = qsimcirq.QSimSimulator() +result = sim.run(noisy_circuit, repetitions=1000) +# Outputs a histogram dict of result:count pairs. +# Expected result is a bunch of 0s and 3s, with fewer 1s and 2s. +# (For comparison, the noiseless circuit will only have 0s and 3s) +print(result.histogram(key='m')) +``` There are many more examples of circuits to be run [here](https://quantumai.google/cirq/noise) -## Shutting down -When you are done with this tutorial, it is important to remove the resources. You can do this with _terraform_ +## FINAL STEP: Shutting down + +> **IMPORTANT**: To avoid excess billing for this project, it is important to shut down the cluster. This is easily done using the make command built for this purpose. ``` make destroy ``` + > The most effective way to ensure you are not charged is to delete the project. [The instructions are here.](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) From 0b058c13a98daeb46c7961b824e344a6c8b0b2ac Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Mon, 30 Aug 2021 22:26:20 +0000 Subject: [PATCH 083/246] Typo --- docs/tutorials/multinode/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index 3d8a34ef..53072b94 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -38,7 +38,7 @@ the Cloud Console with Cloud Shell activeated can be reached through this link: ### Clone this repo -In your Clous Shell window, clone this Github repo. +In your Cloud Shell window, clone this Github repo. ``` bash git clone https://github.com/jrossthomson/qsim.git ``` From 0066b3fa60cf74b3798a3640cc0901d86aa4ec05 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Tue, 31 Aug 2021 14:39:20 +0000 Subject: [PATCH 084/246] Moved readme to upper directory. --- docs/tutorials/multinode/terraform/README.md | 331 +------------------ 1 file changed, 1 insertion(+), 330 deletions(-) diff --git a/docs/tutorials/multinode/terraform/README.md b/docs/tutorials/multinode/terraform/README.md index 3d8a34ef..094f1ee8 100644 --- a/docs/tutorials/multinode/terraform/README.md +++ b/docs/tutorials/multinode/terraform/README.md @@ -1,332 +1,3 @@ # Multinode quantum simulation using HTCondor on GCP -This tutorial will take you through the process of running multiple simultaneous -`qsim` simulations on Google Cloud. In some situations, it is required to run -many instances of the same simulation. This could be used to provide a parameter -sweep or to evaluation noise characteristics. - -One of the key competencies of a quantum computing effort is the ability to run -simulations. While quantum computing hardware is still years from general -availability, quantum computing simulation with `qsim` and `Cirq` is available for -researchers exploring a quantum program. It is expected that simulation will be -a gating requirement to make use of physical hardware, to prove out algorithms -both for computability and stability under noisy conditions. Many thousands of -simulation runs will typically be required to prove out the gating requirements -for each circuit, sometimes requiring years of simulation in advance of any -practical use of the quantum hardware. - -## Objectives - -* Use `terraform` to deploy a HTCondor cluster -* Run a multinode simulation using HTCondor -* Query cluster information and monitor running jobs in HTCondor -* Use `terraform` to destroy the cluster - - -## Step 1: Create a project -In a seperate tutorial the method to create Google Cloud project is explained in detail. -[Please refer to this tutorial for guidance](https://quantumai.google/qsim/tutorials/qsimcirq_gcp#gcp_setup). - -When you have a project created, move on to the next step. - -## Step 2: Configure your enviroment - -Although this tutorial can be run from your local computer, we recommend the use -of [Google Cloud Shell](https://cloud.google.com/shell). Cloud Shell has many useful tools pre-installed. - -Once you have created a project in the previous step, -the Cloud Console with Cloud Shell activeated can be reached through this link: (https://console.google.com/home/dashboard?cloudshell=true) - -### Clone this repo - -In your Clous Shell window, clone this Github repo. -``` bash -git clone https://github.com/jrossthomson/qsim.git -``` - -### Change directory -Change directory to the tutorial: -``` bash -cd qsim/docs/tutorials/multinode/terraform -``` -This is where you will use `terraform` to create the HTCondor cluster required to run your jobs. - -### Edit `init.sh` file to customize your environment - -You can now edit `init.sh` to change the name of the project you are using. You -can also change the zone and region as you see fit. More information is -available [here](https://cloud.google.com/compute/docs/regions-zones). - -Use your favorite text file editor, either the integrated [Cloud Shell -Editor](https://cloud.google.com/shell/docs/editor-overview), `vim`, `emacs` or `nano`. -For example: -``` bash -vim init.sh -``` -The file has many lines, but only edit the first 4. -``` bash -export TF_VAR_project=quantum-htcondor-15 -export TF_VAR_project_id=us-east4-c -export TF_VAR_zone=us-east4-c -export TF_VAR_region=us-east4 -``` - -The most important is the first line, indicating the name of the project you created above. -``` bash -export TF_VAR_project=my-quantum-htcondor-project -``` -The project id needs to be globally unique and to be the same as the project you just created. - -### Source the `init.sh` file -The edited `init.sh` file should be "sourced" in the cloud shell: - -``` bash -source init.sh -``` -Respond `Agree` to any pop-ups that request permissions on the Google Cloud platform. - -The final outcome of this script will include: - -* A gcloud config setup correctly -* A service account created -* The appropriate permissions assigned to the service account -* A key file created to enable the use of Google Cloud automation. - -This will take up to 60 seconds. At the end you will see output about -permissions and the configuration of the account. - -## Step 3: Run terraform - -After the previous steps are completed, you can initialize `terraform` to begin your cluster creation. -The first step is to initialize the `terraform` state. -``` bash -terraform init -``` -A successful result will contain the text: -``` -Terraform has been successfully initialized! -``` -### Run the `make` command -For convenience, some terraform commands are prepared in a `Makefile`. This means -you can now create your cluster, with a simple `make` command. -```bash -make apply -``` -A successful run will show: -``` -Apply complete! Resources: 4 added, 0 changed, 0 destroyed. -``` -## Step4: Connect to the _submit_ node for HTCondor -Although there are ways to run `HTCondor` commands from your local machine, -the normal path is to login to the _submit_ node. From there you can run -commands to submit and monitor jobs on HTCondor. - -### List VMs that were created by - -You can list the VMs created. One of them will be the submit node. It will be the VM with -"submit" in the name. - -``` -gcloud compute instances list -``` -You will see two instances listed: -* c-manager: the core controller node -* c-submit: the submit node where you will run interactive work. - -Identify then log in to the `submit` node. If you used the standard install, the -cluster name is "c". In that case you would connect using the `gcloud ssh` -command. -```bash -gcloud compute ssh c-submit -``` -Now you are logged in to your HTCondor cluster. You will see a command prompt -something like: -```bash -[mylogin@c-submit ~]$ -``` - -### Checking the status -You can verify if the HTCondor install is completed: -``` -condor_q -``` -You will see output: -``` --- Schedd: c-submit.c.quantum-htcondor-14.internal : <10.150.0.2:9618?... @ 08/18/21 18:37:50 -OWNER BATCH_NAME SUBMITTED DONE RUN IDLE HOLD TOTAL JOB_IDS - -Total for query: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended -Total for drj: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended -Total for all users: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended -``` -If you get `command not found`, you will need to wait a few minutes for the HTCondor install to complete. - -## Step 5: Get the sample code and run it -The HTCondor cluster is now ready for your jobs to be run. If you are familiar with HTCondor and you want to run your own jobs, you may do so. - -If you don't have jobs to run, you can get sample jobs from this Github repo. You will clone the repo to -the submit node and run a job. - -## Clone the repo on your cluster - -on the `submit` node you you can install the repo to get access to previously created submission files: -``` -git clone https://github.com/jrossthomson/qsim.git -``` -Then cd to the tutorial directory. -``` -cd qsim/docs/tutorials/multinode -``` -### Submit a job -Now it is possible to submit a job: -``` -condor_submit circuit_q24.sub -``` -If successful, the output will be: -``` -Submitting job(s). -1 job(s) submitted to cluster 1. -``` -You can see the job in queue by with the `condor_q` command. -``` -condor_q -``` -The job will take several minutes to finish. The time includes creating a VM -compute node, installing the HTCondor system and running the job. When completed -the files will be in the `out` directory. -``` -ls out -``` -will list the files: -``` -err.1-0 log.1-0 out.1-0 placeholder -``` -You can also see the progress of the job throught the log file: -``` -tail -f out/log.1-0 -``` -After the job s completed, the ouput of the job can be seen: -``` -cat out/out.1-0 -``` -## Step5: Run multi-node noise simulations -Noise simulations make use of a [Monte Carlo -method](https://en.wikipedia.org/wiki/Monte_Carlo_method) for [quantum -trajectories](https://en.wikipedia.org/wiki/Quantum_Trajectory_Theory). - -The primary goal of these simulations is to understand the behavior of the -quantum circuit taking into account the effects of noise. There are many -potential sources of noise in a quantum circuit, including thermal noise, 1/f -noise or shot noise, as well as perturbations from cosmic rays. Understanding -and calibrating noise in quantum computing is an active field of investigation, -and it will require well characterized simulations. - -To run multiple simulations, you can run the submit file `noise.sub`. The -submit file is shown below. It is key to note that this is running a `docker` -container. Also, the `queue 50` command at the end of the file submits 50 -separate instances of the container. Since this job is noise driven, the output -of each simulation with be different. A typical analysis of the output would be -a statistical study of the means and variations, perhaps looking for parameter -ranges where circuits are particularly susceptible to noise. - - -### The noise.sub file -``` -universe = docker -docker_image = gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest -arguments = python3 noise3.py -should_transfer_files = YES -transfer_input_files = noise3.py -when_to_transfer_output = ON_EXIT -output = out/out.$(Cluster)-$(Process) -error = out/err.$(Cluster)-$(Process) -log = out/log.$(Cluster)-$(Process) -request_memory = 10GB -queue 50 -``` -The job can be submitted with the `condor_submit` command. -``` -condor_submit noise.sub -``` -The output will look as follows: -``` -Submitting job(s).................................................. -50 job(s) submitted to cluster 2. -``` -To monitor the ongoing process of jobs running, you can take advantage of the -Linux `watch` command and run `condor_q` repeatedly. When complete you watch can -be exited with cntrl-c. -``` -watch "condor_q; condor_status" -``` -The output of this command will show you the jobs in the queue as well as the -VMs being created to run the jobs. There is a limit of 20 VMs for this -configuration of the cluster. This can be modified for future runs. - -When the command shows the queue is empty, the command can be stopped with cntl-c. - -If this is the second _submit_ you have run, you can see the output of the all -the simualtions. The output will be in the `out` directory. - -``` -cat out/out.2-* -``` -You can see the results of the simulations. -``` -Counter({3: 462, 0: 452, 2: 50, 1: 36}) -Counter({0: 475, 3: 435, 1: 49, 2: 41}) -Counter({0: 450, 3: 440, 1: 59, 2: 51}) -Counter({0: 459, 3: 453, 2: 51, 1: 37}) -Counter({3: 471, 0: 450, 2: 46, 1: 33}) -Counter({3: 467, 0: 441, 1: 54, 2: 38}) -Counter({3: 455, 0: 455, 1: 50, 2: 40}) -Counter({3: 466, 0: 442, 2: 51, 1: 41}) -. -. -. -``` -Note that because this is a noise driven circuit, the results of each simulation are different. - -## Next steps - -To run your own simulations, simply create a noisy circuit in your _qsim_ python file. -The file being run in this was `noise3.py`. You can take this example and expand -to your own circuit. - -```python -import cirq, qsimcirq - -# Create a Bell state, |00) + |11) -q0, q1 = cirq.LineQubit.range(2) -circuit = cirq.Circuit( - cirq.H(q0), - cirq.CNOT(q0, q1), - cirq.measure(q0, q1, key='m') -) - -# Constructs a noise model that adds depolarizing noise after each gate. -noise = cirq.NoiseModel.from_noise_model_like(cirq.depolarize(p=0.05)) - -# Use the noise model to create a noisy circuit. -noisy_circuit = cirq.Circuit(noise.noisy_moments(circuit, system_qubits=[q0, q1])) - -sim = qsimcirq.QSimSimulator() -result = sim.run(noisy_circuit, repetitions=1000) -# Outputs a histogram dict of result:count pairs. -# Expected result is a bunch of 0s and 3s, with fewer 1s and 2s. -# (For comparison, the noiseless circuit will only have 0s and 3s) -print(result.histogram(key='m')) -``` - -There are many more examples of circuits to be run [here](https://quantumai.google/cirq/noise) - -## FINAL STEP: Shutting down - -> **IMPORTANT**: To avoid excess billing for this project, it is important to shut down the cluster. This is easily done using the make command built for this purpose. -``` -make destroy -``` - -> The most effective way to ensure you are not charged is to delete the project. [The instructions are here.](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) - - +Please refer to the [README in the parent directory](../README.md). \ No newline at end of file From ab099c46e54fdb2bbf0e1579b68be308dfe1eec8 Mon Sep 17 00:00:00 2001 From: J Ross Thomson <39315853+jrossthomson@users.noreply.github.com> Date: Tue, 31 Aug 2021 10:41:32 -0400 Subject: [PATCH 085/246] Update README.md Small indent change. --- docs/tutorials/multinode/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index 53072b94..341d4e39 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -166,7 +166,7 @@ The HTCondor cluster is now ready for your jobs to be run. If you are familiar w If you don't have jobs to run, you can get sample jobs from this Github repo. You will clone the repo to the submit node and run a job. -## Clone the repo on your cluster +### Clone the repo on your cluster on the `submit` node you you can install the repo to get access to previously created submission files: ``` From 31106cecae583aff79003933a3ee77a442f80308 Mon Sep 17 00:00:00 2001 From: Burt Holzman Date: Tue, 7 Sep 2021 15:49:01 -0500 Subject: [PATCH 086/246] Fix typo in multinode README for cloudshell URL --- docs/tutorials/multinode/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index 341d4e39..a9c49cec 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -34,7 +34,7 @@ Although this tutorial can be run from your local computer, we recommend the use of [Google Cloud Shell](https://cloud.google.com/shell). Cloud Shell has many useful tools pre-installed. Once you have created a project in the previous step, -the Cloud Console with Cloud Shell activeated can be reached through this link: (https://console.google.com/home/dashboard?cloudshell=true) +the Cloud Console with Cloud Shell activeated can be reached through this link: (https://console.cloud.google.com/home/dashboard?cloudshell=true) ### Clone this repo From f0ed97c20410adb487342c3a79e94d3341f585f9 Mon Sep 17 00:00:00 2001 From: Burt Holzman Date: Fri, 10 Sep 2021 11:02:50 -0500 Subject: [PATCH 087/246] Add support for regional instance groups To enable regional (rather than zonal) instance groups, set TF_VAR_multizone to True and TF_VAR_numzones to the number of zones in the region. --- .../terraform/htcondor/autoscaler.py | 46 +++++++++--- .../multinode/terraform/htcondor/resources.tf | 75 +++++++++++++++---- .../terraform/htcondor/startup-centos.sh | 3 +- docs/tutorials/multinode/terraform/init.sh | 3 +- docs/tutorials/multinode/terraform/main.tf | 13 ++++ 5 files changed, 110 insertions(+), 30 deletions(-) diff --git a/docs/tutorials/multinode/terraform/htcondor/autoscaler.py b/docs/tutorials/multinode/terraform/htcondor/autoscaler.py index 12036066..151d8d5e 100644 --- a/docs/tutorials/multinode/terraform/htcondor/autoscaler.py +++ b/docs/tutorials/multinode/terraform/htcondor/autoscaler.py @@ -31,6 +31,8 @@ parser = argparse.ArgumentParser() parser.add_argument("--p", required=True, help="Project id", type=str) parser.add_argument("--z", required=True, help="Name of GCP zone where the managed instance group is located", type=str) +parser.add_argument("--r", required=True, help="Name of GCP region where the managed instance group is located", type=str) +parser.add_argument("--mz", required=False, help="Enabled multizone (regional) managed instance group", action="store_true") parser.add_argument("--g", required=True, help="Name of the managed instance group", type=str) parser.add_argument("--c", required=True, help="Maximum number of compute instances", type=int) parser.add_argument("--v", default=0, help="Increase output verbosity. 1-show basic debug info. 2-show detail debug info", type=int, choices=[0, 1, 2]) @@ -41,23 +43,33 @@ class AutoScaler(): - def __init__(self): + def __init__(self, multizone=False): + self.multizone = multizone # Obtain credentials self.credentials = GoogleCredentials.get_application_default() self.service = discovery.build('compute', 'v1', credentials=self.credentials) + if self.multizone: + self.instanceGroupManagers = self.service.regionInstanceGroupManagers() + else: + self.instanceGroupManagers = self.service.instanceGroupManagers() # Remove specified instance from MIG and decrease MIG size def deleteFromMig(self, instance): - instanceUrl = 'https://www.googleapis.com/compute/v1/projects/' \ - + self.project + '/zones/' + self.zone + '/instances/' + instance + instanceUrl = 'https://www.googleapis.com/compute/v1/projects/' + self.project + if (self.multizone): + instanceUrl += '/regions/' + self.region + else: + instanceUrl += '/zones/' + self.zone + instanceUrl += '/instances/' + instance + instances_to_delete = {'instances': [instanceUrl]} requestDelInstance = \ - self.service.instanceGroupManagers().deleteInstances(project=self.project, - zone=self.zone, instanceGroupManager=self.instance_group_manager, - body=instances_to_delete) + self.instanceGroupManagers.deleteInstances(project=self.project, + **self.zoneargs, instanceGroupManager=self.instance_group_manager, + body=instances_to_delete) # execute if not a dry-run if not self.dryrun: @@ -70,7 +82,7 @@ def deleteFromMig(self, instance): def getInstanceTemplateInfo(self): requestTemplateName = \ - self.service.instanceGroupManagers().get(project=self.project, zone=self.zone, + self.instanceGroupManagers.get(project=self.project, **self.zoneargs, instanceGroupManager=self.instance_group_manager, fields='instanceTemplate') responseTemplateName = requestTemplateName.execute() @@ -123,10 +135,17 @@ def scale(self): print('Launching autoscaler.py with the following arguments:') print('project_id: ' + self.project) print('zone: ' + self.zone) + print('region: ' + self.region) + print(f'multizone: {self.multizone}') print('group_manager: ' + self.instance_group_manager) print('computeinstancelimit: ' + str(self.compute_instance_limit)) print('debuglevel: ' + str(self.debug)) + if self.multizone: + self.zoneargs = {'region': self.region} + else: + self.zoneargs = {'zone': self.zone} + # Get total number of jobs in the queue that includes number of jos waiting as well as number of jobs already assigned to nodes queue_length_req = 'condor_q -totals -format "%d " Jobs -format "%d " Idle -format "%d " Held' queue_length_resp = os.popen(queue_length_req).read().split() @@ -180,8 +199,8 @@ def scale(self): print('New MIG target size: ' + str(self.size)) # Get current number of instances in the MIG - requestGroupInfo = self.service.instanceGroupManagers().get(project=self.project, - zone=self.zone, instanceGroupManager=self.instance_group_manager) + requestGroupInfo = self.instanceGroupManagers.get(project=self.project, + **self.zoneargs, instanceGroupManager=self.instance_group_manager) responseGroupInfo = requestGroupInfo.execute() currentTarget = int(responseGroupInfo['targetSize']) print('Current MIG target size: ' + str(currentTarget)) @@ -248,8 +267,8 @@ def scale(self): if self.size > currentTarget: print("Scaling up. Need to increase number of instances to " + str(self.size)) #Request to resize - request = self.service.instanceGroupManagers().resize(project=self.project, - zone=self.zone, + request = self.instanceGroupManagers.resize(project=self.project, + **self.zoneargs, instanceGroupManager=self.instance_group_manager, size=self.size) response = request.execute() @@ -259,7 +278,7 @@ def scale(self): print("Scaling up complete") def main(): - scaler = AutoScaler() + scaler = AutoScaler(args.mz) # Project ID scaler.project = args.p # Ex:'slurm-var-demo' @@ -267,6 +286,9 @@ def main(): # Name of the zone where the managed instance group is located scaler.zone = args.z # Ex: 'us-central1-f' + # Name of the region where the managed instance group is located + scaler.region = args.r # Ex: 'us-central1' + # The name of the managed instance group. scaler.instance_group_manager = args.g # Ex: 'condor-compute-igm' diff --git a/docs/tutorials/multinode/terraform/htcondor/resources.tf b/docs/tutorials/multinode/terraform/htcondor/resources.tf index 42cff48e..0b4212d9 100644 --- a/docs/tutorials/multinode/terraform/htcondor/resources.tf +++ b/docs/tutorials/multinode/terraform/htcondor/resources.tf @@ -28,6 +28,15 @@ variable "project" { variable "zone" { type = string } +variable "region" { + type = string +} +variable "numzones" { + type = string +} +variable "multizone" { + type = bool +} variable "min_replicas" { type = number default = 0 @@ -67,11 +76,13 @@ locals{ { "project" = var.project, "cluster_name" = var.cluster_name, - "htserver_type" = "compute", - "osversion" = var.osversion, - "zone" = var.zone, - "condorversion" = var.condorversion, - "max_replicas" = var.max_replicas, + "htserver_type" = "compute", + "osversion" = var.osversion, + "zone" = var.zone, + "region" = var.region, + "multizone" = var.multizone, + "condorversion" = var.condorversion, + "max_replicas" = var.max_replicas, "autoscaler" = "", "admin_email" = var.admin_email }) @@ -80,11 +91,13 @@ locals{ { "project" = var.project, "cluster_name" = var.cluster_name, - "htserver_type" = "submit", - "osversion" = var.osversion, - "condorversion" = var.condorversion, - "zone" = var.zone, - "max_replicas" = var.max_replicas, + "htserver_type" = "submit", + "osversion" = var.osversion, + "condorversion" = var.condorversion, + "zone" = var.zone, + "region" = var.region, + "multizone" = var.multizone, + "max_replicas" = var.max_replicas, "autoscaler" = local.autoscaler, "admin_email" = var.admin_email }) @@ -93,11 +106,13 @@ locals{ { "project" = var.project, "cluster_name" = var.cluster_name, - "htserver_type" = "manager", - "osversion" = var.osversion, - "zone" = var.zone, - "max_replicas" = var.max_replicas, - "condorversion" = var.condorversion, + "htserver_type" = "manager", + "osversion" = var.osversion, + "zone" = var.zone, + "region" = var.region, + "multizone" = var.multizone, + "max_replicas" = var.max_replicas, + "condorversion" = var.condorversion, "autoscaler" = "", "admin_email" = var.admin_email }) @@ -268,6 +283,7 @@ resource "google_compute_instance_template" "condor-compute" { tags = ["${var.cluster_name}-compute"] } resource "google_compute_instance_group_manager" "condor-compute-igm" { + count = var.multizone ? 0 : 1 base_instance_name = var.cluster_name name = var.cluster_name @@ -294,6 +310,35 @@ resource "google_compute_instance_group_manager" "condor-compute-igm" { ] zone = var.zone } + +resource "google_compute_region_instance_group_manager" "condor-compute-igm" { + count = var.multizone ? 1 : 0 + base_instance_name = var.cluster_name + name = var.cluster_name + + project = var.project + target_size = "0" + + update_policy { + max_surge_fixed = var.numzones + minimal_action = "REPLACE" + type = "OPPORTUNISTIC" + } + + version { + instance_template = google_compute_instance_template.condor-compute.self_link + name = "" + } + timeouts { + create = "60m" + delete = "2h" + } + # Yup, didn't want to use this, but I was getting create and destroy errors. + depends_on = [ + google_compute_instance_template.condor-compute + ] + region = var.region +} /* resource "google_compute_autoscaler" "condor-compute-as" { name = "${var.cluster_name}-compute-as" diff --git a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh index 71f5d423..bec6eb6c 100644 --- a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh +++ b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh @@ -169,7 +169,6 @@ fi service google-fluentd restart # Add Python Libraries and Autoscaler - if [ "$SERVER_TYPE" == "submit" ]; then python3 -m pip install --upgrade oauth2client python3 -m pip install --upgrade google-api-python-client @@ -181,7 +180,7 @@ EOFZ # Create cron entry for autoscaler. Log to /var/log/messages -echo "* * * * * python3 /opt/autoscaler.py --p ${project} --z ${zone} --g ${cluster_name} --c ${max_replicas} | logger " |crontab - +echo "* * * * * python3 /opt/autoscaler.py --p ${project} --z ${zone} --r ${region} %{ if multizone }--mz %{ endif }--g ${cluster_name} --c ${max_replicas} | logger " |crontab - fi diff --git a/docs/tutorials/multinode/terraform/init.sh b/docs/tutorials/multinode/terraform/init.sh index 43a6471b..c9d5d4bd 100644 --- a/docs/tutorials/multinode/terraform/init.sh +++ b/docs/tutorials/multinode/terraform/init.sh @@ -4,7 +4,8 @@ export TF_VAR_project=quantum-htcondor-15 export TF_VAR_project_id=us-east4-c export TF_VAR_zone=us-east4-c export TF_VAR_region=us-east4 - +export TF_VAR_multizone=true +export TF_VAR_numzones=4 # for regional/multizone, set to the number of regions in the zone # ---- Do not edit below -----# export TF_VAR_project_id=${TF_VAR_project} diff --git a/docs/tutorials/multinode/terraform/main.tf b/docs/tutorials/multinode/terraform/main.tf index 0c0a8f87..9e92cc74 100644 --- a/docs/tutorials/multinode/terraform/main.tf +++ b/docs/tutorials/multinode/terraform/main.tf @@ -4,6 +4,16 @@ variable "project" { variable "zone" { type=string } +variable "region" { + type=string +} +variable "multizone" { + type=bool +} +variable "numzones" { + type=string +} + variable "cluster_name" { type = string default = "c" @@ -16,6 +26,9 @@ module "htcondor" { cluster_name = var.cluster_name project = var.project zone = var.zone + region = var.region + multizone = var.multizone + numzones = var.numzones osversion = "7" max_replicas=20 min_replicas=0 From 1ddd29ca41f7dec0636ddaf1ab321933e49dbaea Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Wed, 15 Sep 2021 17:40:45 +0200 Subject: [PATCH 088/246] Add host specifier. --- lib/util_cuda.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/util_cuda.h b/lib/util_cuda.h index 43da065c..591d852d 100644 --- a/lib/util_cuda.h +++ b/lib/util_cuda.h @@ -34,15 +34,16 @@ inline void ErrorAssert(cudaError_t code, const char* file, unsigned line) { template struct Complex { - __device__ __forceinline__ Complex() {} + __host__ __device__ __forceinline__ Complex() {} - __device__ __forceinline__ Complex(const T& re) : re(re), im(0) {} + __host__ __device__ __forceinline__ Complex(const T& re) : re(re), im(0) {} - __device__ __forceinline__ Complex(const T& re, const T& im) + __host__ __device__ __forceinline__ Complex(const T& re, const T& im) : re(re), im(im) {} template - __device__ __forceinline__ Complex& operator=(const Complex& r) { + __host__ __device__ __forceinline__ Complex& operator=( + const Complex& r) { re = r.re; im = r.im; @@ -54,13 +55,13 @@ struct Complex { }; template -__device__ __forceinline__ Complex operator+( +__host__ __device__ __forceinline__ Complex operator+( const Complex& l, const Complex& r) { return Complex(l.re + r.re, l.im + r.im); } template -__device__ __forceinline__ Complex operator+( +__host__ __device__ __forceinline__ Complex operator+( const Complex& l, const Complex& r) { return Complex(l.re + r.re, l.im + r.im); } From 95da6823ef6e35d651c1ae62123464c53020aa21 Mon Sep 17 00:00:00 2001 From: Ricardo Olenewa Date: Wed, 15 Sep 2021 16:16:34 +0000 Subject: [PATCH 089/246] Split 'Guide and Tutorials' book into 'Tutorials' book and 'Guide' book. #424 --- docs/_book.yaml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/_book.yaml b/docs/_book.yaml index 3f344eb1..ba841ebe 100644 --- a/docs/_book.yaml +++ b/docs/_book.yaml @@ -8,7 +8,14 @@ upper_tabs: lower_tabs: # Subsite tabs other: - - name: "Guide & Tutorials" + - name: "Tutorials" + - title: "Get started with qsimcirq" + path: /qsim/tutorials/qsimcirq + - title: "Quantum simulation on GCP with Cirq and qsim" + path: /qsim/tutorials/qsimcirq_gcp + - title: "Simulate a large quantum circuit" + path: /qsim/tutorials/q32d14 + - name: "Guide" contents: - title: "qsim and qsimh" path: /qsim/overview @@ -30,15 +37,6 @@ upper_tabs: path: /qsim/docker - title: "Release process" path: /qsim/release - - - heading: "Tutorials" - - title: "Get started with qsimcirq" - path: /qsim/tutorials/qsimcirq - - title: "Quantum simulation on GCP with Cirq and qsim" - path: /qsim/tutorials/qsimcirq_gcp - - title: "Simulate a large quantum circuit" - path: /qsim/tutorials/q32d14 - - name: "Python Reference" skip_translation: true contents: From 9109c87ce3c7a82129c3038db9c9a4df296b3f89 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 15 Sep 2021 13:19:22 -0700 Subject: [PATCH 090/246] Update version to 0.10.3 --- qsimcirq/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq/_version.py b/qsimcirq/_version.py index d9e1c77d..0d4f6478 100644 --- a/qsimcirq/_version.py +++ b/qsimcirq/_version.py @@ -1,3 +1,3 @@ """The version number defined here is read automatically in setup.py.""" -__version__ = "0.10.2" +__version__ = "0.10.3" From 60235289ff27b5ac0264d42b90b72f5c07972deb Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Thu, 16 Sep 2021 13:56:07 +0000 Subject: [PATCH 091/246] Fix typos --- docs/tutorials/multinode/README.md | 37 +++++++++++++++++++----------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index a9c49cec..17675b37 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -2,7 +2,7 @@ This tutorial will take you through the process of running multiple simultaneous `qsim` simulations on Google Cloud. In some situations, it is required to run many instances of the same simulation. This could be used to provide a parameter -sweep or to evaluation noise characteristics. +sweep or to evaluate noise characteristics. One of the key competencies of a quantum computing effort is the ability to run simulations. While quantum computing hardware is still years from general @@ -17,18 +17,29 @@ practical use of the quantum hardware. ## Objectives * Use `terraform` to deploy a HTCondor cluster -* Run a multinode simulation using HTCondor +* Run a multi-node simulation using HTCondor * Query cluster information and monitor running jobs in HTCondor * Use `terraform` to destroy the cluster ## Step 1: Create a project -In a seperate tutorial the method to create Google Cloud project is explained in detail. -[Please refer to this tutorial for guidance](https://quantumai.google/qsim/tutorials/qsimcirq_gcp#gcp_setup). +If necessary, you can run this tutorial in an existing project, but to avoid collisions or damage to existing work, it is often better to create a new project. + +In this [GCP setup tutorial](https://quantumai.google/qsim/tutorials/qsimcirq_gcp#gcp_setup), the method to create Google Cloud project is explained in detail. When you have a project created, move on to the next step. -## Step 2: Configure your enviroment +> IMPORTANT: For this tutorial, it is best if you have `Owner` privilege on the project. If you do not have owner rights, the following roles will be required: +```bash +roles/compute.admin +roles/iam.serviceAccountUser +roles/monitoring.admin +roles/logging.admin +roles/autoscaling.metricsWrite +``` +Your Cloud admin will be able to provide these roles. + +## Step 2: Configure your environment Although this tutorial can be run from your local computer, we recommend the use of [Google Cloud Shell](https://cloud.google.com/shell). Cloud Shell has many useful tools pre-installed. @@ -186,7 +197,7 @@ If successful, the output will be: Submitting job(s). 1 job(s) submitted to cluster 1. ``` -You can see the job in queue by with the `condor_q` command. +You can see the job in queue with the `condor_q` command. ``` condor_q ``` @@ -200,11 +211,11 @@ will list the files: ``` err.1-0 log.1-0 out.1-0 placeholder ``` -You can also see the progress of the job throught the log file: +You can also see the progress of the job through the log file: ``` tail -f out/log.1-0 ``` -After the job s completed, the ouput of the job can be seen: +After the job is completed, the output of the job can be seen: ``` cat out/out.1-0 ``` @@ -224,7 +235,7 @@ To run multiple simulations, you can run the submit file `noise.sub`. The submit file is shown below. It is key to note that this is running a `docker` container. Also, the `queue 50` command at the end of the file submits 50 separate instances of the container. Since this job is noise driven, the output -of each simulation with be different. A typical analysis of the output would be +of each simulation will be different. A typical analysis of the output would be a statistical study of the means and variations, perhaps looking for parameter ranges where circuits are particularly susceptible to noise. @@ -253,7 +264,7 @@ Submitting job(s).................................................. 50 job(s) submitted to cluster 2. ``` To monitor the ongoing process of jobs running, you can take advantage of the -Linux `watch` command and run `condor_q` repeatedly. When complete you watch can +Linux `watch` command and run `condor_q` repeatedly. When complete watch can be exited with cntrl-c. ``` watch "condor_q; condor_status" @@ -262,10 +273,10 @@ The output of this command will show you the jobs in the queue as well as the VMs being created to run the jobs. There is a limit of 20 VMs for this configuration of the cluster. This can be modified for future runs. -When the command shows the queue is empty, the command can be stopped with cntl-c. +When the command shows the queue is empty, the command can be stopped with cntrl-c. -If this is the second _submit_ you have run, you can see the output of the all -the simualtions. The output will be in the `out` directory. +If this is the second _submit_ you have run, you can see the output of all +the simulations. The output will be in the `out` directory. ``` cat out/out.2-* From 1957a4f779e5a5b9625cc2f951496393f672c353 Mon Sep 17 00:00:00 2001 From: Ross Thomson Date: Thu, 16 Sep 2021 14:16:13 +0000 Subject: [PATCH 092/246] Removed un-needed files. --- docs/_book.yaml | 2 ++ docs/tutorials/multinode/noise.py | 51 ------------------------------ docs/tutorials/multinode/noise2.py | 18 ----------- 3 files changed, 2 insertions(+), 69 deletions(-) delete mode 100644 docs/tutorials/multinode/noise.py delete mode 100644 docs/tutorials/multinode/noise2.py diff --git a/docs/_book.yaml b/docs/_book.yaml index 3f344eb1..dd0838e1 100644 --- a/docs/_book.yaml +++ b/docs/_book.yaml @@ -38,6 +38,8 @@ upper_tabs: path: /qsim/tutorials/qsimcirq_gcp - title: "Simulate a large quantum circuit" path: /qsim/tutorials/q32d14 + - title: "Multinode quantum simulation using HTCondor on GCP" + path: /qsim/tutorials/multinode - name: "Python Reference" skip_translation: true diff --git a/docs/tutorials/multinode/noise.py b/docs/tutorials/multinode/noise.py deleted file mode 100644 index ecbd751d..00000000 --- a/docs/tutorials/multinode/noise.py +++ /dev/null @@ -1,51 +0,0 @@ -import cirq -import qsimcirq -import time - -q0, q1 = cirq.LineQubit.range(2) - -circuit = cirq.Circuit( - # Perform a Hadamard on both qubits - cirq.H(q0), cirq.H(q1), - # Apply amplitude damping to q0 with probability 0.1 - cirq.amplitude_damp(gamma=0.1).on(q0), - # Apply phase damping to q1 with probability 0.1 - cirq.phase_damp(gamma=0.1).on(q1), -) -qsim_simulator = qsimcirq.QSimSimulator() -results = qsim_simulator.simulate(circuit) -print(results.final_state_vector) - - -# Simulate measuring at the end of the circuit. -measured_circuit = circuit + cirq.measure(q0, q1, key='m') -measure_results = qsim_simulator.run(measured_circuit, repetitions=5) -print(measure_results) - -# Calculate only the amplitudes of the |00) and |01) states. -amp_results = qsim_simulator.compute_amplitudes( - circuit, bitstrings=[0b00, 0b01]) -print(amp_results) - -# Calculate only the amplitudes of the |00) and |01) states. -amp_results = qsim_simulator.compute_amplitudes( - circuit, bitstrings=[0b00, 0b01]) -print(amp_results) - - -# Set the "noisy repetitions" to 100. -# This parameter only affects expectation value calculations. -options = {'r': 100} -# Also set the random seed to get reproducible results. -seed = int(time.time()) -print(seed) -ev_simulator = qsimcirq.QSimSimulator(qsim_options=options, seed=seed) -# Define observables to measure: for q0 and for q1. -pauli_sum1 = cirq.Z(q0) -pauli_sum2 = cirq.X(q1) -# Calculate expectation values for the given observables. -ev_results = ev_simulator.simulate_expectation_values( - circuit, - observables=[pauli_sum1, pauli_sum2], -) -print(ev_results) \ No newline at end of file diff --git a/docs/tutorials/multinode/noise2.py b/docs/tutorials/multinode/noise2.py deleted file mode 100644 index db593b7a..00000000 --- a/docs/tutorials/multinode/noise2.py +++ /dev/null @@ -1,18 +0,0 @@ -import cirq -import qsimcirq -import time - -circuit = cirq.testing.random_circuit( - qubits=3, n_moments=3, op_density=1, random_state=11 -) - -# Display the noiseless circuit. -print("Circuit without noise:") -print(circuit) - -# Add noise to the circuit. -noisy = circuit.with_noise(cirq.depolarize(p=0.01)) - -# Display it. -print("\nCircuit with noise:") -print(noisy) \ No newline at end of file From e7e67a0cebf39dcb25501b85310f887f67fb6097 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 16 Sep 2021 12:36:58 -0700 Subject: [PATCH 093/246] Prevent ODR violations in util.h --- lib/util.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/util.h b/lib/util.h index 19fbb9fd..933730c3 100644 --- a/lib/util.h +++ b/lib/util.h @@ -91,7 +91,7 @@ inline std::vector GenerateRandomValues( // This function sets flush-to-zero and denormals-are-zeros MXCSR control // flags. This prevents rare cases of performance slowdown potentially at // the cost of a tiny precision loss. -void SetFlushToZeroAndDenormalsAreZeros() { +inline void SetFlushToZeroAndDenormalsAreZeros() { #ifdef __SSE2__ _mm_setcsr(_mm_getcsr() | 0x8040); #endif @@ -99,7 +99,7 @@ void SetFlushToZeroAndDenormalsAreZeros() { // This function clears flush-to-zero and denormals-are-zeros MXCSR control // flags. -void ClearFlushToZeroAndDenormalsAreZeros() { +inline void ClearFlushToZeroAndDenormalsAreZeros() { #ifdef __SSE2__ _mm_setcsr(_mm_getcsr() & ~unsigned{0x8040}); #endif From 7839ff3333fbcebb763c24eab0c149e218429353 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 21 Sep 2021 08:45:32 -0700 Subject: [PATCH 094/246] Initial technical fixes --- docs/tutorials/multinode/README.md | 60 ++++--------------- docs/tutorials/multinode/circuit_q24.sub | 11 ---- .../{circuit_q30.sub => noiseless.sub} | 8 +-- docs/tutorials/multinode/noiseless3.py | 15 +++++ 4 files changed, 32 insertions(+), 62 deletions(-) delete mode 100644 docs/tutorials/multinode/circuit_q24.sub rename docs/tutorials/multinode/{circuit_q30.sub => noiseless.sub} (74%) create mode 100644 docs/tutorials/multinode/noiseless3.py diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index 17675b37..f7ab47e8 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -4,16 +4,6 @@ This tutorial will take you through the process of running multiple simultaneous many instances of the same simulation. This could be used to provide a parameter sweep or to evaluate noise characteristics. -One of the key competencies of a quantum computing effort is the ability to run -simulations. While quantum computing hardware is still years from general -availability, quantum computing simulation with `qsim` and `Cirq` is available for -researchers exploring a quantum program. It is expected that simulation will be -a gating requirement to make use of physical hardware, to prove out algorithms -both for computability and stability under noisy conditions. Many thousands of -simulation runs will typically be required to prove out the gating requirements -for each circuit, sometimes requiring years of simulation in advance of any -practical use of the quantum hardware. - ## Objectives * Use `terraform` to deploy a HTCondor cluster @@ -190,7 +180,7 @@ cd qsim/docs/tutorials/multinode ### Submit a job Now it is possible to submit a job: ``` -condor_submit circuit_q24.sub +condor_submit noiseless.sub ``` If successful, the output will be: ``` @@ -234,10 +224,10 @@ and it will require well characterized simulations. To run multiple simulations, you can run the submit file `noise.sub`. The submit file is shown below. It is key to note that this is running a `docker` container. Also, the `queue 50` command at the end of the file submits 50 -separate instances of the container. Since this job is noise driven, the output -of each simulation will be different. A typical analysis of the output would be -a statistical study of the means and variations, perhaps looking for parameter -ranges where circuits are particularly susceptible to noise. +separate instances of the container. Each simulation will be different due to +the stochastic nature of noisy simulations. A typical analysis of the output +would be a statistical study of the means and variations, perhaps looking for +parameter ranges where circuits are particularly susceptible to noise. ### The noise.sub file @@ -295,49 +285,25 @@ Counter({3: 466, 0: 442, 2: 51, 1: 41}) . . ``` -Note that because this is a noise driven circuit, the results of each simulation are different. ## Next steps -To run your own simulations, simply create a noisy circuit in your _qsim_ python file. -The file being run in this was `noise3.py`. You can take this example and expand -to your own circuit. - -```python -import cirq, qsimcirq - -# Create a Bell state, |00) + |11) -q0, q1 = cirq.LineQubit.range(2) -circuit = cirq.Circuit( - cirq.H(q0), - cirq.CNOT(q0, q1), - cirq.measure(q0, q1, key='m') -) - -# Constructs a noise model that adds depolarizing noise after each gate. -noise = cirq.NoiseModel.from_noise_model_like(cirq.depolarize(p=0.05)) - -# Use the noise model to create a noisy circuit. -noisy_circuit = cirq.Circuit(noise.noisy_moments(circuit, system_qubits=[q0, q1])) - -sim = qsimcirq.QSimSimulator() -result = sim.run(noisy_circuit, repetitions=1000) -# Outputs a histogram dict of result:count pairs. -# Expected result is a bunch of 0s and 3s, with fewer 1s and 2s. -# (For comparison, the noiseless circuit will only have 0s and 3s) -print(result.histogram(key='m')) -``` +The file being run in the previous example was `noise3.py`. To run your own +simulations, simply create a new python file with your circuit and change the +`noise3.py` references in `noise.sub` to point to the new file. -There are many more examples of circuits to be run [here](https://quantumai.google/cirq/noise) +A detailed discussion of how to construct various types of noise in Cirq can be +found [here](https://quantumai.google/cirq/noise). -## FINAL STEP: Shutting down +## Final step: Shutting down > **IMPORTANT**: To avoid excess billing for this project, it is important to shut down the cluster. This is easily done using the make command built for this purpose. ``` make destroy ``` -> The most effective way to ensure you are not charged is to delete the project. [The instructions are here.](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) +> The most effective way to prevent further charges is to delete the project. +[The instructions are here.](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) diff --git a/docs/tutorials/multinode/circuit_q24.sub b/docs/tutorials/multinode/circuit_q24.sub deleted file mode 100644 index a8d6b3ff..00000000 --- a/docs/tutorials/multinode/circuit_q24.sub +++ /dev/null @@ -1,11 +0,0 @@ -universe = docker -docker_image = gcr.io/quantum-builds/github.com/quantumlib/qsim -arguments = -c circuit_q24 -transfer_input_files = ../../../circuits/circuit_q24 -should_transfer_files = YES -when_to_transfer_output = ON_EXIT -output = out/out.$(Cluster)-$(Process) -error = out/err.$(Cluster)-$(Process) -log = out/log.$(Cluster)-$(Process) -request_memory = 1GB -queue 1 diff --git a/docs/tutorials/multinode/circuit_q30.sub b/docs/tutorials/multinode/noiseless.sub similarity index 74% rename from docs/tutorials/multinode/circuit_q30.sub rename to docs/tutorials/multinode/noiseless.sub index 756295c7..f4bde2c8 100644 --- a/docs/tutorials/multinode/circuit_q30.sub +++ b/docs/tutorials/multinode/noiseless.sub @@ -1,11 +1,11 @@ universe = docker -docker_image = gcr.io/quantum-builds/github.com/quantumlib/qsim -arguments = -c circuit_q30 -transfer_input_files = ../../../circuits/circuit_q30 +docker_image = gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest +arguments = python3 noiseless3.py should_transfer_files = YES +transfer_input_files = noiseless3.py when_to_transfer_output = ON_EXIT output = out/out.$(Cluster)-$(Process) error = out/err.$(Cluster)-$(Process) log = out/log.$(Cluster)-$(Process) request_memory = 10GB -queue 1 +queue 1 \ No newline at end of file diff --git a/docs/tutorials/multinode/noiseless3.py b/docs/tutorials/multinode/noiseless3.py new file mode 100644 index 00000000..4960ccdb --- /dev/null +++ b/docs/tutorials/multinode/noiseless3.py @@ -0,0 +1,15 @@ +import cirq, qsimcirq + +# Create a Bell state, |00) + |11) +q0, q1 = cirq.LineQubit.range(2) +circuit = cirq.Circuit( + cirq.H(q0), + cirq.CNOT(q0, q1), + cirq.measure(q0, q1, key='m') +) + +sim = qsimcirq.QSimSimulator() +result = sim.run(circuit, repetitions=1000) +# Outputs a histogram dict of result:count pairs. +# Expected result is a bunch of 0s and 3s, with no 1s or 2s. +print(result.histogram(key='m')) \ No newline at end of file From 95804984180714c4951bc429e3d9371d5d793950 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 21 Sep 2021 08:47:32 -0700 Subject: [PATCH 095/246] Autoformat python --- docs/tutorials/multinode/noise3.py | 8 +- docs/tutorials/multinode/noiseless3.py | 8 +- .../terraform/htcondor/autoscaler.py | 349 ++++++++++-------- .../multinode/terraform/htcondor/noise.py | 8 +- 4 files changed, 210 insertions(+), 163 deletions(-) diff --git a/docs/tutorials/multinode/noise3.py b/docs/tutorials/multinode/noise3.py index 38d741a3..f3abbdd5 100644 --- a/docs/tutorials/multinode/noise3.py +++ b/docs/tutorials/multinode/noise3.py @@ -2,11 +2,7 @@ # Create a Bell state, |00) + |11) q0, q1 = cirq.LineQubit.range(2) -circuit = cirq.Circuit( - cirq.H(q0), - cirq.CNOT(q0, q1), - cirq.measure(q0, q1, key='m') -) +circuit = cirq.Circuit(cirq.H(q0), cirq.CNOT(q0, q1), cirq.measure(q0, q1, key="m")) # Constructs a noise model that adds depolarizing noise after each gate. noise = cirq.NoiseModel.from_noise_model_like(cirq.depolarize(p=0.05)) @@ -19,4 +15,4 @@ # Outputs a histogram dict of result:count pairs. # Expected result is a bunch of 0s and 3s, with fewer 1s and 2s. # (For comparison, the noiseless circuit will only have 0s and 3s) -print(result.histogram(key='m')) \ No newline at end of file +print(result.histogram(key="m")) diff --git a/docs/tutorials/multinode/noiseless3.py b/docs/tutorials/multinode/noiseless3.py index 4960ccdb..f35dcb5c 100644 --- a/docs/tutorials/multinode/noiseless3.py +++ b/docs/tutorials/multinode/noiseless3.py @@ -2,14 +2,10 @@ # Create a Bell state, |00) + |11) q0, q1 = cirq.LineQubit.range(2) -circuit = cirq.Circuit( - cirq.H(q0), - cirq.CNOT(q0, q1), - cirq.measure(q0, q1, key='m') -) +circuit = cirq.Circuit(cirq.H(q0), cirq.CNOT(q0, q1), cirq.measure(q0, q1, key="m")) sim = qsimcirq.QSimSimulator() result = sim.run(circuit, repetitions=1000) # Outputs a histogram dict of result:count pairs. # Expected result is a bunch of 0s and 3s, with no 1s or 2s. -print(result.histogram(key='m')) \ No newline at end of file +print(result.histogram(key="m")) diff --git a/docs/tutorials/multinode/terraform/htcondor/autoscaler.py b/docs/tutorials/multinode/terraform/htcondor/autoscaler.py index 151d8d5e..7795d8c9 100644 --- a/docs/tutorials/multinode/terraform/htcondor/autoscaler.py +++ b/docs/tutorials/multinode/terraform/htcondor/autoscaler.py @@ -30,126 +30,165 @@ parser = argparse.ArgumentParser() parser.add_argument("--p", required=True, help="Project id", type=str) -parser.add_argument("--z", required=True, help="Name of GCP zone where the managed instance group is located", type=str) -parser.add_argument("--r", required=True, help="Name of GCP region where the managed instance group is located", type=str) -parser.add_argument("--mz", required=False, help="Enabled multizone (regional) managed instance group", action="store_true") -parser.add_argument("--g", required=True, help="Name of the managed instance group", type=str) -parser.add_argument("--c", required=True, help="Maximum number of compute instances", type=int) -parser.add_argument("--v", default=0, help="Increase output verbosity. 1-show basic debug info. 2-show detail debug info", type=int, choices=[0, 1, 2]) -parser.add_argument("--d", default=0, help="Dry Run, default=0, if 1, then no scaling actions", type=int, choices=[0, 1]) +parser.add_argument( + "--z", + required=True, + help="Name of GCP zone where the managed instance group is located", + type=str, +) +parser.add_argument( + "--r", + required=True, + help="Name of GCP region where the managed instance group is located", + type=str, +) +parser.add_argument( + "--mz", + required=False, + help="Enabled multizone (regional) managed instance group", + action="store_true", +) +parser.add_argument( + "--g", required=True, help="Name of the managed instance group", type=str +) +parser.add_argument( + "--c", required=True, help="Maximum number of compute instances", type=int +) +parser.add_argument( + "--v", + default=0, + help="Increase output verbosity. 1-show basic debug info. 2-show detail debug info", + type=int, + choices=[0, 1, 2], +) +parser.add_argument( + "--d", + default=0, + help="Dry Run, default=0, if 1, then no scaling actions", + type=int, + choices=[0, 1], +) args = parser.parse_args() - -class AutoScaler(): + +class AutoScaler: def __init__(self, multizone=False): self.multizone = multizone # Obtain credentials self.credentials = GoogleCredentials.get_application_default() - self.service = discovery.build('compute', 'v1', credentials=self.credentials) - + self.service = discovery.build("compute", "v1", credentials=self.credentials) + if self.multizone: self.instanceGroupManagers = self.service.regionInstanceGroupManagers() else: self.instanceGroupManagers = self.service.instanceGroupManagers() - + # Remove specified instance from MIG and decrease MIG size def deleteFromMig(self, instance): - instanceUrl = 'https://www.googleapis.com/compute/v1/projects/' + self.project - if (self.multizone): - instanceUrl += '/regions/' + self.region + instanceUrl = "https://www.googleapis.com/compute/v1/projects/" + self.project + if self.multizone: + instanceUrl += "/regions/" + self.region else: - instanceUrl += '/zones/' + self.zone - instanceUrl += '/instances/' + instance + instanceUrl += "/zones/" + self.zone + instanceUrl += "/instances/" + instance + + instances_to_delete = {"instances": [instanceUrl]} - instances_to_delete = {'instances': [instanceUrl]} - - requestDelInstance = \ - self.instanceGroupManagers.deleteInstances(project=self.project, - **self.zoneargs, instanceGroupManager=self.instance_group_manager, - body=instances_to_delete) + requestDelInstance = self.instanceGroupManagers.deleteInstances( + project=self.project, + **self.zoneargs, + instanceGroupManager=self.instance_group_manager, + body=instances_to_delete, + ) # execute if not a dry-run if not self.dryrun: response = requestDelInstance.execute() if self.debug > 0: - print('Request to delete instance ' + instance) + print("Request to delete instance " + instance) pprint(response) return response return "Dry Run" - + def getInstanceTemplateInfo(self): - requestTemplateName = \ - self.instanceGroupManagers.get(project=self.project, **self.zoneargs, - instanceGroupManager=self.instance_group_manager, - fields='instanceTemplate') + requestTemplateName = self.instanceGroupManagers.get( + project=self.project, + **self.zoneargs, + instanceGroupManager=self.instance_group_manager, + fields="instanceTemplate", + ) responseTemplateName = requestTemplateName.execute() - template_name = '' - + template_name = "" + if self.debug > 1: - print('Request for the template name') + print("Request for the template name") pprint(responseTemplateName) - + if len(responseTemplateName) > 0: - template_url = responseTemplateName.get('instanceTemplate') - template_url_partitioned = template_url.split('/') - template_name = \ - template_url_partitioned[len(template_url_partitioned) - 1] - - requestInstanceTemplate = \ - self.service.instanceTemplates().get(project=self.project, - instanceTemplate=template_name, fields='properties') + template_url = responseTemplateName.get("instanceTemplate") + template_url_partitioned = template_url.split("/") + template_name = template_url_partitioned[len(template_url_partitioned) - 1] + + requestInstanceTemplate = self.service.instanceTemplates().get( + project=self.project, instanceTemplate=template_name, fields="properties" + ) responseInstanceTemplateInfo = requestInstanceTemplate.execute() - + if self.debug > 1: - print('Template information') - pprint(responseInstanceTemplateInfo['properties']) - - machine_type = responseInstanceTemplateInfo['properties']['machineType'] - is_preemtible = responseInstanceTemplateInfo['properties']['scheduling']['preemptible'] + print("Template information") + pprint(responseInstanceTemplateInfo["properties"]) + + machine_type = responseInstanceTemplateInfo["properties"]["machineType"] + is_preemtible = responseInstanceTemplateInfo["properties"]["scheduling"][ + "preemptible" + ] if self.debug > 0: - print('Machine Type: ' + machine_type) - print('Is preemtible: ' + str(is_preemtible)) - request = self.service.machineTypes().get(project=self.project, zone=self.zone, - machineType=machine_type) + print("Machine Type: " + machine_type) + print("Is preemtible: " + str(is_preemtible)) + request = self.service.machineTypes().get( + project=self.project, zone=self.zone, machineType=machine_type + ) response = request.execute() - guest_cpus = response['guestCpus'] + guest_cpus = response["guestCpus"] if self.debug > 1: - print('Machine information') - pprint(responseInstanceTemplateInfo['properties']) + print("Machine information") + pprint(responseInstanceTemplateInfo["properties"]) if self.debug > 0: - print('Guest CPUs: ' + str(guest_cpus)) - - instanceTemlateInfo = {'machine_type': machine_type, - 'is_preemtible': is_preemtible, - 'guest_cpus': guest_cpus} + print("Guest CPUs: " + str(guest_cpus)) + + instanceTemlateInfo = { + "machine_type": machine_type, + "is_preemtible": is_preemtible, + "guest_cpus": guest_cpus, + } return instanceTemlateInfo - - def scale(self): # diagnosis if self.debug > 1: - print('Launching autoscaler.py with the following arguments:') - print('project_id: ' + self.project) - print('zone: ' + self.zone) - print('region: ' + self.region) - print(f'multizone: {self.multizone}') - print('group_manager: ' + self.instance_group_manager) - print('computeinstancelimit: ' + str(self.compute_instance_limit)) - print('debuglevel: ' + str(self.debug)) + print("Launching autoscaler.py with the following arguments:") + print("project_id: " + self.project) + print("zone: " + self.zone) + print("region: " + self.region) + print(f"multizone: {self.multizone}") + print("group_manager: " + self.instance_group_manager) + print("computeinstancelimit: " + str(self.compute_instance_limit)) + print("debuglevel: " + str(self.debug)) if self.multizone: - self.zoneargs = {'region': self.region} + self.zoneargs = {"region": self.region} else: - self.zoneargs = {'zone': self.zone} + self.zoneargs = {"zone": self.zone} # Get total number of jobs in the queue that includes number of jos waiting as well as number of jobs already assigned to nodes - queue_length_req = 'condor_q -totals -format "%d " Jobs -format "%d " Idle -format "%d " Held' + queue_length_req = ( + 'condor_q -totals -format "%d " Jobs -format "%d " Idle -format "%d " Held' + ) queue_length_resp = os.popen(queue_length_req).read().split() - + if len(queue_length_resp) > 1: queue = int(queue_length_resp[0]) idle_jobs = int(queue_length_resp[1]) @@ -158,70 +197,85 @@ def scale(self): queue = 0 idle_jobs = 0 on_hold_jobs = 0 - - print('Total queue length: ' + str(queue)) - print('Idle jobs: ' + str(idle_jobs)) - print('Jobs on hold: ' + str(on_hold_jobs)) - + + print("Total queue length: " + str(queue)) + print("Idle jobs: " + str(idle_jobs)) + print("Jobs on hold: " + str(on_hold_jobs)) + instanceTemlateInfo = self.getInstanceTemplateInfo() if self.debug > 1: - print('Information about the compute instance template') + print("Information about the compute instance template") pprint(instanceTemlateInfo) - - self.cores_per_node = instanceTemlateInfo['guest_cpus'] - print('Number of CPU per compute node: ' + str(self.cores_per_node)) - + + self.cores_per_node = instanceTemlateInfo["guest_cpus"] + print("Number of CPU per compute node: " + str(self.cores_per_node)) + # Get state for for all jobs in Condor - name_req = 'condor_status -af name state' + name_req = "condor_status -af name state" slot_names = os.popen(name_req).read().splitlines() if self.debug > 1: - print('Currently running jobs in Condor') + print("Currently running jobs in Condor") print(slot_names) - + # Adjust current queue length by the number of jos that are on-hold - queue -=on_hold_jobs - if on_hold_jobs>0: + queue -= on_hold_jobs + if on_hold_jobs > 0: print("Adjusted queue length: " + str(queue)) - + # Calculate number instances to satisfy current job queue length if queue > 0: self.size = int(math.ceil(float(queue) / float(self.cores_per_node))) - if self.debug>0: - print("Calucalting size of MIG: ⌈" + str(queue) + "/" + str(self.cores_per_node) + "⌉ = " + str(self.size)) + if self.debug > 0: + print( + "Calucalting size of MIG: ⌈" + + str(queue) + + "/" + + str(self.cores_per_node) + + "⌉ = " + + str(self.size) + ) else: self.size = 0 - + # If compute instance limit is specified, can not start more instances then specified in the limit if self.compute_instance_limit > 0 and self.size > self.compute_instance_limit: self.size = self.compute_instance_limit - print("MIG target size will be limited by " + str(self.compute_instance_limit)) - - print('New MIG target size: ' + str(self.size)) - + print( + "MIG target size will be limited by " + str(self.compute_instance_limit) + ) + + print("New MIG target size: " + str(self.size)) + # Get current number of instances in the MIG - requestGroupInfo = self.instanceGroupManagers.get(project=self.project, - **self.zoneargs, instanceGroupManager=self.instance_group_manager) + requestGroupInfo = self.instanceGroupManagers.get( + project=self.project, + **self.zoneargs, + instanceGroupManager=self.instance_group_manager, + ) responseGroupInfo = requestGroupInfo.execute() - currentTarget = int(responseGroupInfo['targetSize']) - print('Current MIG target size: ' + str(currentTarget)) - + currentTarget = int(responseGroupInfo["targetSize"]) + print("Current MIG target size: " + str(currentTarget)) + if self.debug > 1: - print('MIG Information:') + print("MIG Information:") print(responseGroupInfo) - + if self.size == 0 and currentTarget == 0: - print('No jobs in the queue and no compute instances running. Nothing to do') + print( + "No jobs in the queue and no compute instances running. Nothing to do" + ) exit() - + if self.size == currentTarget: - print('Running correct number of compute nodes to handle number of jobs in the queue') + print( + "Running correct number of compute nodes to handle number of jobs in the queue" + ) exit() - - + if self.size < currentTarget: - print('Scaling down. Looking for nodes that can be shut down' ) + print("Scaling down. Looking for nodes that can be shut down") # Find nodes that are not busy (all slots showing status as "Unclaimed") - + node_busy = {} for slot_name in slot_names: name_status = slot_name.split() @@ -229,91 +283,96 @@ def scale(self): name = name_status[0] status = name_status[1] slot = "NO-SLOT" - slot_server = name.split('@') + slot_server = name.split("@") if len(slot_server) > 1: slot = slot_server[0] - server = slot_server[1].split('.')[0] + server = slot_server[1].split(".")[0] else: - server = slot_server[0].split('.')[0] - + server = slot_server[0].split(".")[0] + if self.debug > 0: - print(slot + ', ' + server + ', ' + status + '\n') - + print(slot + ", " + server + ", " + status + "\n") + if server not in node_busy: - if status == 'Unclaimed': + if status == "Unclaimed": node_busy[server] = False else: node_busy[server] = True else: - if status != 'Unclaimed': + if status != "Unclaimed": node_busy[server] = True - + if self.debug > 1: - print('Compuute node busy status:') + print("Compuute node busy status:") print(node_busy) - + # Shut down nodes that are not busy for node in node_busy: if not node_busy[node]: - print('Will shut down: ' + node + ' ...') + print("Will shut down: " + node + " ...") respDel = self.deleteFromMig(node) if self.debug > 1: print("Shut down request for compute node " + node) pprint(respDel) - + if self.debug > 1: print("Scaling down complete") - + if self.size > currentTarget: - print("Scaling up. Need to increase number of instances to " + str(self.size)) - #Request to resize - request = self.instanceGroupManagers.resize(project=self.project, - **self.zoneargs, - instanceGroupManager=self.instance_group_manager, - size=self.size) + print( + "Scaling up. Need to increase number of instances to " + str(self.size) + ) + # Request to resize + request = self.instanceGroupManagers.resize( + project=self.project, + **self.zoneargs, + instanceGroupManager=self.instance_group_manager, + size=self.size, + ) response = request.execute() if self.debug > 1: - print('Requesting to increase MIG size') + print("Requesting to increase MIG size") pprint(response) print("Scaling up complete") + + def main(): scaler = AutoScaler(args.mz) # Project ID scaler.project = args.p # Ex:'slurm-var-demo' - + # Name of the zone where the managed instance group is located - scaler.zone = args.z # Ex: 'us-central1-f' - + scaler.zone = args.z # Ex: 'us-central1-f' + # Name of the region where the managed instance group is located - scaler.region = args.r # Ex: 'us-central1' + scaler.region = args.r # Ex: 'us-central1' # The name of the managed instance group. scaler.instance_group_manager = args.g # Ex: 'condor-compute-igm' - + # Default number of cores per intance, will be replaced with actual value scaler.cores_per_node = 4 - + # Default number of running instances that the managed instance group should maintain at any given time. This number will go up and down based on the load (number of jobs in the queue) scaler.size = 0 # Dry run: : 0, run scaling; 1, only provide info. scaler.dryrun = args.d > 0 - + # Debug level: 1-print debug information, 2 - print detail debug information scaler.debug = 0 - if (args.v): + if args.v: scaler.debug = args.v - - # Limit for the maximum number of compute instance. If zero (default setting), no limit will be enforced by the script + + # Limit for the maximum number of compute instance. If zero (default setting), no limit will be enforced by the script scaler.compute_instance_limit = 0 - if (args.c): + if args.c: scaler.compute_instance_limit = abs(args.c) - - + scaler.scale() if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/docs/tutorials/multinode/terraform/htcondor/noise.py b/docs/tutorials/multinode/terraform/htcondor/noise.py index df87cc55..b5542959 100644 --- a/docs/tutorials/multinode/terraform/htcondor/noise.py +++ b/docs/tutorials/multinode/terraform/htcondor/noise.py @@ -4,11 +4,7 @@ # Create a Bell state, |00) + |11) q0, q1 = cirq.LineQubit.range(2) -circuit = cirq.Circuit( - cirq.H(q0), - cirq.CNOT(q0, q1), - cirq.measure(q0, q1, key='m') -) +circuit = cirq.Circuit(cirq.H(q0), cirq.CNOT(q0, q1), cirq.measure(q0, q1, key="m")) # Constructs a noise model that adds depolarizing noise after each gate. noise = cirq.NoiseModel.from_noise_model_like(cirq.depolarize(p=0.05)) @@ -21,4 +17,4 @@ # Outputs a histogram dict of result:count pairs. # Expected result is a bunch of 0s and 3s, with fewer 1s and 2s. # (For comparison, the noiseless circuit will only have 0s and 3s) -print(result.histogram(key='m')) \ No newline at end of file +print(result.histogram(key="m")) From f607fbcf068978154d41e8ebafcd789bee9db5bd Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Fri, 24 Sep 2021 17:54:03 +0200 Subject: [PATCH 096/246] Add a quantum trajectory app. --- apps/make.sh | 1 + apps/qsim_qtrajectory_cuda.cu | 313 ++++++++++++++++++++++++++++++++++ lib/channels_qsim.h | 103 +++++++++++ lib/gates_qsim.h | 43 +++++ 4 files changed, 460 insertions(+) create mode 100644 apps/qsim_qtrajectory_cuda.cu create mode 100644 lib/channels_qsim.h diff --git a/apps/make.sh b/apps/make.sh index 679694f8..e21f410c 100755 --- a/apps/make.sh +++ b/apps/make.sh @@ -24,3 +24,4 @@ g++ -O3 -march=native -fopenmp -o qsimh_base.x qsimh_base.cc g++ -O3 -march=native -fopenmp -o qsimh_amplitudes.x qsimh_amplitudes.cc nvcc -O3 -o qsim_base_cuda.x qsim_base_cuda.cu +nvcc -O3 -o qsim_qtrajectory_cuda.x qsim_qtrajectory_cuda.cu diff --git a/apps/qsim_qtrajectory_cuda.cu b/apps/qsim_qtrajectory_cuda.cu new file mode 100644 index 00000000..f5636c0f --- /dev/null +++ b/apps/qsim_qtrajectory_cuda.cu @@ -0,0 +1,313 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../lib/channels_qsim.h" +#include "../lib/circuit_qsim_parser.h" +#include "../lib/expect.h" +#include "../lib/fuser_mqubit.h" +#include "../lib/gates_qsim.h" +#include "../lib/io_file.h" +#include "../lib/qtrajectory.h" +#include "../lib/simulator_cuda.h" + +struct Options { + std::string circuit_file; + std::vector times = {std::numeric_limits::max()}; + double amplitude_damp_const = 0; + double phase_damp_const = 0; + unsigned traj0 = 0; + unsigned num_trajectories = 10; + unsigned max_fused_size = 2; + unsigned verbosity = 0; +}; + +constexpr char usage[] = "usage:\n ./qsim_qtrajectory_cuda.x " + "-c circuit_file -d times_to_calculate_observables " + "-a amplitude_damping_const -p phase_damping_const " + "-t traj0 -n num_trajectories -f max_fused_size " + "-v verbosity\n"; + +Options GetOptions(int argc, char* argv[]) { + Options opt; + + int k; + + auto to_int = [](const std::string& word) -> unsigned { + return std::atoi(word.c_str()); + }; + + while ((k = getopt(argc, argv, "c:d:a:p:t:n:f:v:")) != -1) { + switch (k) { + case 'c': + opt.circuit_file = optarg; + break; + case 'd': + qsim::SplitString(optarg, ',', to_int, opt.times); + break; + case 'a': + opt.amplitude_damp_const = std::atof(optarg); + break; + case 'p': + opt.phase_damp_const = std::atof(optarg); + break; + case 't': + opt.traj0 = std::atoi(optarg); + break; + case 'n': + opt.num_trajectories = std::atoi(optarg); + break; + case 'f': + opt.max_fused_size = std::atoi(optarg); + break; + case 'v': + opt.verbosity = std::atoi(optarg); + break; + break; + default: + qsim::IO::errorf(usage); + exit(1); + } + } + + return opt; +} + +bool ValidateOptions(const Options& opt) { + if (opt.circuit_file.empty()) { + qsim::IO::errorf("circuit file is not provided.\n"); + qsim::IO::errorf(usage); + return false; + } + + if (opt.times.size() == 0) { + qsim::IO::errorf("times to calculate observables are not provided.\n"); + return false; + } + + for (std::size_t i = 1; i < opt.times.size(); i++) { + if (opt.times[i - 1] == opt.times[i]) { + qsim::IO::errorf("duplicate times to calculate observables.\n"); + return false; + } else if (opt.times[i - 1] > opt.times[i]) { + qsim::IO::errorf("times to calculate observables are not sorted.\n"); + return false; + } + } + + return true; +} + +template +std::vector> AddNoise( + const qsim::Circuit& circuit, const std::vector& times, + const Channel1& channel1, const Channel2& channel2) { + std::vector> ncircuits; + ncircuits.reserve(times.size()); + + qsim::NoisyCircuit ncircuit; + + ncircuit.num_qubits = circuit.num_qubits; + ncircuit.channels.reserve(5 * circuit.gates.size()); + + unsigned cur_time_index = 0; + + for (std::size_t i = 0; i < circuit.gates.size(); ++i) { + const auto& gate = circuit.gates[i]; + + ncircuit.channels.push_back(qsim::MakeChannelFromGate(3 * gate.time, gate)); + + for (auto q : gate.qubits) { + ncircuit.channels.push_back(channel1.Create(3 * gate.time + 1, q)); + } + + for (auto q : gate.qubits) { + ncircuit.channels.push_back(channel2.Create(3 * gate.time + 2, q)); + } + + unsigned t = times[cur_time_index]; + + if (i == circuit.gates.size() - 1 || t < circuit.gates[i + 1].time) { + ncircuits.push_back(std::move(ncircuit)); + + ncircuit = {}; + + if (i < circuit.gates.size() - 1) { + if (circuit.gates[i + 1].time > times.back()) { + break; + } + + ncircuit.num_qubits = circuit.num_qubits; + ncircuit.channels.reserve(5 * circuit.gates.size()); + } + + ++cur_time_index; + } + } + + return ncircuits; +} + +template +std::vector>> GetObservables( + unsigned num_qubits) { + std::vector>> observables; + observables.reserve(num_qubits); + + using X = qsim::GateX; + + for (unsigned q = 0; q < num_qubits; ++q) { + observables.push_back({{{1.0, 0.0}, {X::Create(0, q)}}}); + } + + return observables; +} + +int main(int argc, char* argv[]) { + using namespace qsim; + + using fp_type = float; + + struct Factory { + using Simulator = qsim::SimulatorCUDA; + using StateSpace = Simulator::StateSpace; + + Factory(const StateSpace::Parameter& param1, + const Simulator::Parameter& param2) + : param1(param1), param2(param2) {} + + StateSpace CreateStateSpace() const { + return StateSpace(param1); + } + + Simulator CreateSimulator() const { + return Simulator(param2); + } + + const StateSpace::Parameter& param1; + const Simulator::Parameter& param2; + }; + + using Simulator = Factory::Simulator; + using StateSpace = Simulator::StateSpace; + using State = StateSpace::State; + using Fuser = MultiQubitGateFuser>; + using QTSimulator = QuantumTrajectorySimulator, + MultiQubitGateFuser, + Simulator>; + + auto opt = GetOptions(argc, argv); + if (!ValidateOptions(opt)) { + return 1; + } + + Circuit> circuit; + unsigned maxtime = opt.times.back(); + if (!CircuitQsimParser::FromFile(maxtime, opt.circuit_file, + circuit)) { + return 1; + } + + StateSpace::Parameter param1; + Simulator::Parameter param2; + Factory factory(param1, param2); + + Simulator simulator = factory.CreateSimulator(); + StateSpace state_space = factory.CreateStateSpace(); + + State state = state_space.Create(circuit.num_qubits); + State scratch = state_space.Null(); + + if (state_space.IsNull(state)) { + IO::errorf("not enough memory: is the number of qubits too large?\n"); + return 1; + } + + typename QTSimulator::Parameter param3; + param3.max_fused_size = opt.max_fused_size; + param3.verbosity = opt.verbosity; + + auto channel1 = AmplitudeDampingChannel(opt.amplitude_damp_const); + auto channel2 = PhaseDampingChannel(opt.phase_damp_const); + + auto noisy_circuits = AddNoise(circuit, opt.times, channel1, channel2); + + auto observables = GetObservables>(circuit.num_qubits); + + std::vector>>> results; + results.reserve(noisy_circuits.size()); + + for (unsigned i = 0; i < opt.num_trajectories; ++i) { + results.push_back({}); + results[i].reserve(noisy_circuits.size()); + + state_space.SetStateZero(state); + + auto seed = noisy_circuits.size() * (i + opt.traj0); + + for (unsigned s = 0; s < noisy_circuits.size(); ++s) { + std::vector stat; + if (!QTSimulator::RunOnce(param3, noisy_circuits[s], seed++, + state_space, simulator, scratch, state, + stat)) { + return 1; + } + + results[i].push_back({}); + results[i][s].reserve(observables.size()); + + for (const auto& obs : observables) { + results[i][s].push_back( + ExpectationValue(obs, simulator, state)); + } + } + } + + for (unsigned i = 1; i < opt.num_trajectories; ++i) { + for (unsigned s = 0; s < noisy_circuits.size(); ++s) { + for (unsigned k = 0; k < observables.size(); ++k) { + results[0][s][k] += results[i][s][k]; + } + } + } + + double f = 1.0 / opt.num_trajectories; + + for (unsigned s = 0; s < noisy_circuits.size(); ++s) { + for (unsigned k = 0; k < observables.size(); ++k) { + results[0][s][k] *= f; + } + } + + for (unsigned s = 0; s < noisy_circuits.size(); ++s) { + IO::messagef("#time=%u\n", opt.times[s]); + + for (unsigned k = 0; k < observables.size(); ++k) { + IO::messagef("%4u %4u %17.9g %17.9g\n", s, k, + std::real(results[0][s][k]), std::imag(results[0][s][k])); + } + } + + return 0; +} diff --git a/lib/channels_qsim.h b/lib/channels_qsim.h new file mode 100644 index 00000000..9630de07 --- /dev/null +++ b/lib/channels_qsim.h @@ -0,0 +1,103 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CHANNELS_QSIM_H_ +#define CHANNELS_QSIM_H_ + +#include +#include +#include + +#include "channel.h" +#include "gates_qsim.h" + +namespace qsim { + +/** + * Amplitude damping channel factory. + */ +template +struct AmplitudeDampingChannel { + AmplitudeDampingChannel(double gamma) : gamma(gamma) {} + + static Channel> Create( + unsigned time, unsigned q, double gamma) { + double p1 = 1 - gamma; + double p2 = 0; + + fp_type r = std::sqrt(p1); + fp_type s = std::sqrt(gamma); + + using M = GateMatrix1; + auto normal = KrausOperator>::kNormal; + + return {{normal, 0, p1, {M::Create(time, q, {1, 0, 0, 0, 0, 0, r, 0})}}, + {normal, 0, p2, {M::Create(time, q, {0, 0, s, 0, 0, 0, 0, 0})}}}; + } + + Channel> Create(unsigned time, unsigned q) const { + return Create(time, q, gamma); + } + + double gamma = 0; +}; + +/** + * Returns an amplitude damping channel factory object. + */ +template +inline AmplitudeDampingChannel amplitude_damp(double gamma) { + return AmplitudeDampingChannel(gamma); +} + +/** + * Phase damping channel factory. + */ +template +struct PhaseDampingChannel { + PhaseDampingChannel(double gamma) : gamma(gamma) {} + + static Channel> Create( + unsigned time, unsigned q, double gamma) { + double p1 = 1 - gamma; + double p2 = 0; + + fp_type r = std::sqrt(p1); + fp_type s = std::sqrt(gamma); + + using M = GateMatrix1; + auto normal = KrausOperator>::kNormal; + + return {{normal, 0, p1, {M::Create(time, q, {1, 0, 0, 0, 0, 0, r, 0})}}, + {normal, 0, p2, {M::Create(time, q, {0, 0, 0, 0, 0, 0, s, 0})}}}; + } + + Channel> Create(unsigned time, unsigned q) const { + return Create(time, q, gamma); + } + + double gamma = 0; +}; + +/** + * Returns a phase damping channel factory object. + */ +template +inline PhaseDampingChannel phase_damp(double gamma) { + return PhaseDampingChannel(gamma); +} + +} // namespace qsim + +#endif // CHANNELS_QSIM_H_ diff --git a/lib/gates_qsim.h b/lib/gates_qsim.h index 7690c2b6..9b64cf98 100644 --- a/lib/gates_qsim.h +++ b/lib/gates_qsim.h @@ -46,6 +46,8 @@ enum GateKind { kGateIS, // iSwap kGateFS, // fSim kGateCP, // control phase + kGateMatrix1, // One-qubit matrix gate. + kGateMatrix2, // Two-qubit matrix gate. kDecomp = gate::kDecomp, kMeasurement = gate::kMeasurement, }; @@ -317,6 +319,24 @@ struct GateS { } }; +/** + * A one-qubit gate defined entirely by its matrix. + */ +template +struct GateMatrix1 { + static constexpr GateKind kind = kGateMatrix1; + static constexpr char name[] = "mat1"; + static constexpr unsigned num_qubits = 1; + static constexpr bool symmetric = true; + + static GateQSim Create(unsigned time, unsigned q0, + const Matrix& m) { + auto m2 = m; + return + CreateGate, GateMatrix1>(time, {q0}, std::move(m2)); + } +}; + // Two-qubit gates: /** @@ -566,6 +586,29 @@ struct GateCP { } }; +/** + * A two-qubit gate defined entirely by its matrix. + */ +template +struct GateMatrix2 { + static constexpr GateKind kind = kGateMatrix2; + static constexpr char name[] = "mat2"; + static constexpr unsigned num_qubits = 2; + static constexpr bool symmetric = false; + + template > + static GateQSim Create( + unsigned time, unsigned q0, unsigned q1, M&& m) { + return CreateGate, GateMatrix2>(time, {q1, q0}, + std::forward(m)); + } + + static schmidt_decomp_type SchmidtDecomp(fp_type phi) { + // Not implemented. + return schmidt_decomp_type{}; + } +}; + template inline schmidt_decomp_type GetSchmidtDecomp( GateKind kind, const std::vector& params) { From 5c4d98d8475e5c23c1a3d209176210bed384672a Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 24 Sep 2021 10:58:40 -0700 Subject: [PATCH 097/246] Add GPU warning for `simulate_sweep` --- qsimcirq/qsim_simulator.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index a7b52335..b99a110a 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -454,6 +454,11 @@ def simulate_sweep( This method returns a result which allows access to the entire wave function. In contrast to simulate, this allows for sweeping over different parameter values. + + Avoid using this method with `use_gpu=True` in the simulator options; + when used with GPU this method must copy state from device to host memory + multiple times, which can be very slow. This issue is not present in + `simulate_expectation_values_sweep`. Args: program: The circuit to simulate. From 2f1261211c585961cc0079a06657d50f6345b6a2 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 24 Sep 2021 11:10:03 -0700 Subject: [PATCH 098/246] Remove leading whitespace --- qsimcirq/qsim_simulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index b99a110a..aa923a93 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -454,7 +454,7 @@ def simulate_sweep( This method returns a result which allows access to the entire wave function. In contrast to simulate, this allows for sweeping over different parameter values. - + Avoid using this method with `use_gpu=True` in the simulator options; when used with GPU this method must copy state from device to host memory multiple times, which can be very slow. This issue is not present in From 2d24994baaf92a0d4dc5eaaf2ecb751f80273a11 Mon Sep 17 00:00:00 2001 From: Ricardo Olenewa Date: Thu, 30 Sep 2021 15:14:22 +0000 Subject: [PATCH 099/246] Fix an error in _book.yaml; split GCP tutorial into multiple parts to clarify CPU and GPU workflows; added noise simulation tutorial to nav. #424 --- docs/_book.yaml | 97 +++++----- docs/images/colab_connect.png | Bin 0 -> 75964 bytes docs/images/colab_connected.png | Bin 0 -> 32211 bytes docs/images/colab_remote.png | Bin 0 -> 238053 bytes docs/tutorials/gcp_before_you_begin.md | 33 ++++ docs/tutorials/gcp_cpu.md | 247 +++++++++++++++++++++++++ docs/tutorials/gcp_gpu.md | 195 +++++++++++++++++++ 7 files changed, 529 insertions(+), 43 deletions(-) create mode 100644 docs/images/colab_connect.png create mode 100644 docs/images/colab_connected.png create mode 100644 docs/images/colab_remote.png create mode 100644 docs/tutorials/gcp_before_you_begin.md create mode 100644 docs/tutorials/gcp_cpu.md create mode 100644 docs/tutorials/gcp_gpu.md diff --git a/docs/_book.yaml b/docs/_book.yaml index ba841ebe..411999d6 100644 --- a/docs/_book.yaml +++ b/docs/_book.yaml @@ -6,49 +6,60 @@ upper_tabs: menu: - include: /_book/menu_software.yaml lower_tabs: - # Subsite tabs - other: - - name: "Tutorials" - - title: "Get started with qsimcirq" - path: /qsim/tutorials/qsimcirq - - title: "Quantum simulation on GCP with Cirq and qsim" - path: /qsim/tutorials/qsimcirq_gcp - - title: "Simulate a large quantum circuit" - path: /qsim/tutorials/q32d14 - - name: "Guide" - contents: - - title: "qsim and qsimh" - path: /qsim/overview - - title: "Usage" - path: /qsim/usage - - title: "Installing qsimcirq" - path: /qsim/install_qsimcirq - - title: "Cirq interface" - path: /qsim/cirq_interface - - title: "Input circuit file format" - path: /qsim/input_format - - title: "Template naming" - path: /qsim/type_reference - - title: "Build with Bazel" - path: /qsim/bazel - - title: "Testing qsim" - path: /qsim/testing - - title: "Docker" - path: /qsim/docker - - title: "Release process" - path: /qsim/release - - name: "Python Reference" - skip_translation: true - contents: - - title: "All Symbols" - path: /reference/python/qsimcirq/all_symbols - - include: /reference/python/qsimcirq/_toc.yaml + # Subsite tabs + other: + - name: "Tutorials" + contents: + - title: "Get started with qsimcirq" + path: /qsim/tutorials/qsimcirq + - heading: "qsim on Google Cloud" + - title: "Before you begin" + path: /qsim/tutorials/gcp_before_you_begin + - title: "CPU-based simulation" + path: /qsim/tutorials/gcp_cpu + - title: "GPU-based simulation" + path: /qsim/tutorials/gcp_gpu + - heading: "Other tutorials" + - title: "Simulate a large circuit" + path: /qsim/tutorials/q32d14 + - title: "Noise simulation" + path: /qsim/tutorials/noisy_qsimcirq - - name: "C++ Reference" - skip_translation: true - contents: - - title: "All Symbols" - path: /reference/cc/qsim - - include: /reference/cc/qsim/_doxygen.yaml + - name: "Guide" + contents: + - title: "qsim and qsimh" + path: /qsim/overview + - title: "Usage" + path: /qsim/usage + - title: "Installing qsimcirq" + path: /qsim/install_qsimcirq + - title: "Cirq interface" + path: /qsim/cirq_interface + - title: "Input circuit file format" + path: /qsim/input_format + - title: "Template naming" + path: /qsim/type_reference + - title: "Build with Bazel" + path: /qsim/bazel + - title: "Testing qsim" + path: /qsim/testing + - title: "Docker" + path: /qsim/docker + - title: "Release process" + path: /qsim/release + + - name: "Python Reference" + skip_translation: true + contents: + - title: "All Symbols" + path: /reference/python/qsimcirq/all_symbols + - include: /reference/python/qsimcirq/_toc.yaml + + - name: "C++ Reference" + skip_translation: true + contents: + - title: "All Symbols" + path: /reference/cc/qsim + - include: /reference/cc/qsim/_doxygen.yaml - include: /_book/upper_tabs_right.yaml diff --git a/docs/images/colab_connect.png b/docs/images/colab_connect.png new file mode 100644 index 0000000000000000000000000000000000000000..9c74edf91dfa433c6ccb1bd49ceb1c20dfc70b9f GIT binary patch literal 75964 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�P#GEgf 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 F{{a!jH8}tP literal 0 HcmV?d00001 diff --git a/docs/images/colab_remote.png b/docs/images/colab_remote.png new file mode 100644 index 0000000000000000000000000000000000000000..c1614895c837727673cbd7a2e4f4f0fe545647fe GIT binary patch literal 238053 zcmb?@Wmr_-`Zgdcph$_RAl;29&47p?-5o;+NDeS`3W!J}Ig%>UFi3aDAT13;NXO7M zH1Fnj;(tQUr+2Pv*qgmqJ#{~^76dCRO5@`_#>2qCz?XR~sfvMdvl{~gn;7R7@Fe

Ddt@J#>@(lz^dM4n-QMj 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�?tBD&hF3oS5YU{baDmLSE${rFQA2rH>!;t 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

-

qsimh

-

- qsimh is a hybrid Schrödinger-Feynman simulator built for parallel - execution on a cluster of machines. It produces amplitudes for user- - specified output bitstrings. -

buttons: - label: Get started with qsim on Cirq path: /qsim/tutorials/qsimcirq From b98f06352bda589b41e9ed922d4b83580d2172ab Mon Sep 17 00:00:00 2001 From: jlow2397 <54644848+jlow2397@users.noreply.github.com> Date: Wed, 6 Oct 2021 09:34:02 -0700 Subject: [PATCH 115/246] updating copy with feedback --- docs/_index.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_index.yaml b/docs/_index.yaml index 1d657862..064e2b70 100644 --- a/docs/_index.yaml +++ b/docs/_index.yaml @@ -15,7 +15,7 @@ book_path: /qsim/_book.yaml project_path: /qsim/_project.yaml description: > - Quantum circuit simulators qsim. + Quantum circuit simulator qsim. landing_page: custom_css_path: /site-assets/css/style.css rows: From 5f24cf95e9293da1fdc556a14bd06b134f3af001 Mon Sep 17 00:00:00 2001 From: Ricardo Olenewa Date: Wed, 6 Oct 2021 19:50:07 +0000 Subject: [PATCH 116/246] Restructure Guides book. --- docs/_book.yaml | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/docs/_book.yaml b/docs/_book.yaml index 411999d6..77184a50 100644 --- a/docs/_book.yaml +++ b/docs/_book.yaml @@ -22,29 +22,28 @@ upper_tabs: - heading: "Other tutorials" - title: "Simulate a large circuit" path: /qsim/tutorials/q32d14 - - title: "Noise simulation" + - title: "Simulate noise" path: /qsim/tutorials/noisy_qsimcirq - - name: "Guide" + - name: "Guides" contents: - - title: "qsim and qsimh" + - title: "Introduction" path: /qsim/overview - - title: "Usage" + # TODO(cognigami): Uncomment this when you post the choose_hw doc. + # - title: "Choosing hardware" + # path: /qsim/choose_hw + - title: "Command line reference" path: /qsim/usage - - title: "Installing qsimcirq" - path: /qsim/install_qsimcirq - - title: "Cirq interface" + - title: "Python interface" path: /qsim/cirq_interface - - title: "Input circuit file format" - path: /qsim/input_format - - title: "Template naming" + - title: "C++ templates" path: /qsim/type_reference - - title: "Build with Bazel" + - title: "C++ builds with Bazel" path: /qsim/bazel + - title: "Docker builds" + path: /qsim/docker - title: "Testing qsim" path: /qsim/testing - - title: "Docker" - path: /qsim/docker - title: "Release process" path: /qsim/release From b25715ed214efb5744cadc7819a9bb5b2abb7646 Mon Sep 17 00:00:00 2001 From: Ricardo Olenewa Date: Wed, 6 Oct 2021 19:57:04 +0000 Subject: [PATCH 117/246] Restructure Guides book. --- docs/_book.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/_book.yaml b/docs/_book.yaml index 77184a50..21d2c2d0 100644 --- a/docs/_book.yaml +++ b/docs/_book.yaml @@ -62,3 +62,5 @@ upper_tabs: - include: /reference/cc/qsim/_doxygen.yaml - include: /_book/upper_tabs_right.yaml + + From a4609d6dc089da0c70a85a4a8369f73b8f23a8ac Mon Sep 17 00:00:00 2001 From: Ricardo Olenewa Date: Thu, 7 Oct 2021 14:22:47 +0000 Subject: [PATCH 118/246] Add Choosing Hardware guide. Integrate guide into nav, and resolve TODOs in other files. --- docs/_book.yaml | 5 +- docs/choose_hw.md | 933 ++++++++++++++++++++++++ docs/images/choose_hw.png | Bin 0 -> 47955 bytes docs/images/qsim_runtime_comparison.png | Bin 0 -> 201441 bytes docs/tutorials/gcp_cpu.md | 5 +- docs/tutorials/gcp_gpu.md | 2 +- 6 files changed, 938 insertions(+), 7 deletions(-) create mode 100644 docs/choose_hw.md create mode 100644 docs/images/choose_hw.png create mode 100644 docs/images/qsim_runtime_comparison.png diff --git a/docs/_book.yaml b/docs/_book.yaml index 21d2c2d0..9fe4dae8 100644 --- a/docs/_book.yaml +++ b/docs/_book.yaml @@ -29,9 +29,8 @@ upper_tabs: contents: - title: "Introduction" path: /qsim/overview - # TODO(cognigami): Uncomment this when you post the choose_hw doc. - # - title: "Choosing hardware" - # path: /qsim/choose_hw + - title: "Choosing hardware" + path: /qsim/choose_hw - title: "Command line reference" path: /qsim/usage - title: "Python interface" diff --git a/docs/choose_hw.md b/docs/choose_hw.md new file mode 100644 index 00000000..db65950d --- /dev/null +++ b/docs/choose_hw.md @@ -0,0 +1,933 @@ + + +# Choosing hardware for your qsim simulation + +As you increase the size and complexity of your quantum simulation, you rapidly +require a large increase in computational power. This guide describes +considerations that can help you choose hardware for your simulation. + +Your simulation setup depends on the following: + +* Noise; noisy (realistic) simulations require more compute power than + noiseless (idealised) simulations. +* Number of qubits. +* Circuit depth; the number of time steps required to perform the circuit. + +## Quick start + +The following graph provides loose guidelines to help you get started with +choosing hardware for your simulation. The qubit upper bounds in this chart are +not technical limits. + +![Decision tree for hardware to run a qsim simulation.](images/choose_hw.png) + +## Choose hardware for your simulation + +### 1. Evaluate whether your simulation can be run locally. + +If you have a modern laptop with at least 8GB of memory, you can run your +simulation locally in the following cases: + +* Noiseless simulations that use fewer than 29 qubits. +* Noisy simulations that use fewer than 18 qubits. + +If you intend to simulate a circuit many times, consider multinode simulation. +For more information about multinode simulation +[see step 5, below](#5_consider_multiple_compute_nodes). + +### 2. Estimate your memory requirements + +You can estimate your memory requirements with the following rule of thumb: + +* Noiseless simulation: $ memory\ required = 8 \cdot 2^N bytes $ for an N-qubit + circuit +* Noisy simulation: $ memory\ required = 16 \cdot 2^N bytes $ for an N-qubit circuit + +In addition to memory size, consider the bandwidth of your memory. qsim performs +best when it can use the maximum number of threads. Multi-threaded simulation +benefits from high-bandwidth memory (above 100GB/s). + +### 3. Decide between CPUs and GPUs + +* GPU hardware starts to outperform CPU hardware significantly (up to 15x + faster) for circuits with more than 20 qubits. +* The maximum number of qubits that you can simulate with a GPU is limited by + the memory of the GPU. Currently, for a noiseless simulation on an NVIDIA + A100 GPU, the maximum number of qubits is 32. +* For noiseless simulations with 32-40 qubits, you can use CPUs. However, the + runtime time increases exponentially with the number of qubits, and runtimes + are long for simulations above 32 qubits. + +The following chart shows the runtime for a circuit run on +[Google Compute Engine](https://cloud.google.com/compute), using an NVidia A100 +processor, and a compute-optimized processor (c2-standard-4). Each circuit was +run with three different phase damping channel (p) settings: 0, 0.1, and 0.001. +The graph is log scale. + +![qsim runtime comparison on multipe processors and configurations](images/qsim_runtime_comparison.png) + +### 4. Select a specific machine + +After you decide whether you want to use CPUs or GPUs for your simulation, +choose a specific machine: + +1. Restrict your options to machines that meet your memory requirements. For + more information about memory requirements, see step 2. +2. Decide if performance (speed) or cost is more important to you: + * For a table of performance benchmarks, see + [Sample benchmarks](#sample_benchmarks) below. + * For more information about GCP pricing, see the + [Google Cloud pricing calculator](https://cloud.google.com/products/calculator). + * Prioritizing performance is particularly important in the following + scenarios: + * Simulating with an **higher f value** (f is the maximum number of + qubits allowed per fused gate). + * For small to medium size circuits (up to 22 qubits), keep f low + (2 or 3) + * For medium to large size qubits (22+ qubits), use a higher f (3 + or 4) + * Simulating a **deep circuit** (depth 30+). + +### 5. Consider multiple compute nodes + +Simulating in multinode mode is useful when your simulation can be parallelized. +In a noisy simulation, the trajectories (also known as repetitions, iterations) +are “embarrassingly parallelizable”, there is an automated workflow for +distributing these trajectories over multiple nodes. A simulation of many +noiseless circuits can also be distributed over multiple compute nodes. + +## Runtime estimates + +Runtime grows exponentially with the number of qubits, and linearly with circuit +depth beyond 20 qubits. + +* For noiseless simulations, runtime grows at a rate of $ 2^N $ for an N-qubit + circuit. For more information about runtimes for small circuits, see + [Additional notes for advanced users](#additional_notes_for_advanced_users) + below), +* For noisy simulations, runtime grows at a rate of $ 2^N $ multiplied by the + number of iterations for an N-qubit circuit. + +## Additional notes for advanced users + +* The impact of noise on simulation depends on: + * What type of errors are included in your noise channel (decoherence, + depolarizing channels, coherent errors, readout errors). + * How you can represent your noise model using Kraus operator formalism + * Performance is best in the case where all Kraus operators are + proportional to unitary matrices, such as when using only a + depolarizing channel. In this case, memory requirements are equal to + noiseless memory requirements (8\*2^n bytes) + * Using noise which cannot be represented with Kraus operators + proportional to unitary matrices, can slow down simulations by a + factor of up to 6** **compared to using a depolarizing channel only + * Noisy simulations are faster with lower noise (when one Kraus + operator dominates) +* Experimenting with the 'f' parameter (maximum number of qubits allowed per + fused gate): + * The advanced user is advised to try out multiple f values to optimize + their simulation setup. + * Note that f=2 or f=3 can be optimal for large circuits simulated on + CPUs with a smaller number of threads (say, up to four or eight + threads). However, this depends on the circuit structure. + * Note that f=6 is very rarely optimal +* Using the optimal number of threads: + * Use the maximum number of threads on CPUs for the best performance. + * If the maximum number of threads is not used on multi-socket machines + then it is advisable to distribute threads evenly to all sockets or to + run all threads within a single socket. Separate simulations on each + socket can be run simultaneously in the latter case. + * Note that currently the number of CPU threads does not affect the + performance for small circuits (smaller than 17 qubits). Only one thread + is used because of OpenMP overhead. +* Runtime estimates for small circuits: + * For circuits that contain fewer than 20 qubits, the qsimcirq translation + layer performance overhead tends to dominate the runtime estimate. In + addition to this, qsim is not optimized for small circuits + * ​​The total small circuits runtime overhead for an N qubit circuit + depends on the circuit depth and on N, and can be up to orders of + magnitude larger than $ 2^N $. + +## Sample benchmarks + +**Noiseless simulation benchmarks data sheet** + +For a random circuit, depth=20, f=3, max threads. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
processor type + machine + # of qubits + runtime +
CPU + c2-standard-60 + 34 + 291.987 +
CPU + c2-standard-60 + 32 + 54.558 +
CPU + c2-standard-60 + 30 + 13.455 +
CPU + c2-standard-60 + 28 + 2.837 +
CPU + c2-standard-60 + 24 + 0.123 +
CPU + c2-standard-60 + 20 + 0.013 +
CPU + c2-standard-60 + 16 + 0.009 +
CPU + c2-standard-4-4 + 30 + 52.880 +
CPU + c2-standard-4-4 + 28 + 12.814 +
CPU + c2-standard-4-4 + 24 + 0.658 +
CPU + c2-standard-4-4 + 20 + 0.031 +
CPU + c2-standard-4-4 + 16 + 0.008 +
GPU + a100 + 32 + 7.415 +
GPU + a100 + 30 + 1.561 +
GPU + a100 + 28 + 0.384 +
GPU + a100 + 24 + 0.030 +
GPU + a100 + 20 + 0.010 +
GPU + a100 + 16 + 0.007 +
GPU + t4 + 30 + 10.163 +
GPU + t4 + 28 + 2.394 +
GPU + t4 + 24 + 0.118 +
GPU + t4 + 20 + 0.014 +
GPU + t4 + 16 + 0.007 +
+ +**Noisy simulation benchmarks data sheet** + +For one trajectory of a random circuit, depth=20, f=3, max threads. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
processor type + machine + noise type + # of qubits + runtime +
CPU + c2-standard-60 + depolarizing + 30 + 13.021 +
CPU + c2-standard-60 + depolarizing + 28 + 2.840 +
CPU + c2-standard-60 + depolarizing + 26 + 0.604 +
CPU + c2-standard-60 + depolarizing + 24 + 0.110 +
CPU + c2-standard-60 + depolarizing + 20 + 0.009 +
CPU + c2-standard-60 + depolarizing + 16 + 0.006 +
CPU + c2-standard-60 + dephasing + 30 + 122.788 +
CPU + c2-standard-60 + dephasing + 28 + 29.966 +
CPU + c2-standard-60 + dephasing + 26 + 6.378 +
CPU + c2-standard-60 + dephasing + 24 + 1.181 +
CPU + c2-standard-60 + dephasing + 20 + 0.045 +
CPU + c2-standard-60 + dephasing + 16 + 0.023 +
CPU + c2-standard-4-4 + depolarizing + 26 + 2.807 +
CPU + c2-standard-4-4 + depolarizing + 24 + 0.631 +
CPU + c2-standard-4-4 + depolarizing + 20 + 0.027 +
CPU + c2-standard-4-4 + depolarizing + 16 + 0.005 +
CPU + c2-standard-4-4 + dephasing + 26 + 33.038 +
CPU + c2-standard-4-4 + dephasing + 24 + 7.432 +
CPU + c2-standard-4-4 + dephasing + 20 + 0.230 +
CPU + c2-standard-4-4 + dephasing + 16 + 0.014 +
GPU + a100 + depolarizing + 30 + 1.568 +
GPU + a100 + depolarizing + 28 + 0.391 +
GPU + a100 + depolarizing + 26 + 0.094 +
GPU + a100 + depolarizing + 24 + 0.026 +
GPU + a100 + depolarizing + 20 + 0.006 +
GPU + a100 + depolarizing + 16 + 0.004 +
GPU + a100 + dephasing + 30 + 17.032 +
GPU + a100 + dephasing + 28 + 3.959 +
GPU + a100 + dephasing + 26 + 0.896 +
GPU + a100 + dephasing + 24 + 0.236 +
GPU + a100 + dephasing + 20 + 0.029 +
GPU + a100 + dephasing + 16 + 0.021 +
GPU + t4 + depolarizing + 30 + 10.229 +
GPU + t4 + depolarizing + 28 + 2.444 +
GPU + t4 + depolarizing + 26 + 0.519 +
GPU + t4 + depolarizing + 24 + 0.115 +
GPU + t4 + depolarizing + 20 + 0.009 +
GPU + t4 + depolarizing + 16 + 0.004 +
GPU + t4 + dephasing + 28 + 21.800 +
GPU + t4 + dephasing + 26 + 5.056 +
GPU + t4 + dephasing + 24 + 1.164 +
GPU + t4 + dephasing + 20 + 0.077 +
GPU + t4 + dephasing + 16 + 0.017 +
diff --git a/docs/images/choose_hw.png b/docs/images/choose_hw.png new file mode 100644 index 0000000000000000000000000000000000000000..76a894b4805f0510867aca882dcf767c994120c5 GIT binary patch literal 47955 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 literal 0 HcmV?d00001 diff --git a/docs/images/qsim_runtime_comparison.png b/docs/images/qsim_runtime_comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..ac6030742964fdbc92c091b3a28f40e07975d49e GIT binary patch literal 201441 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
p8i|vOLF7F$1)Xjz)q1BRRV&VK7FQ+ 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 x|ECr8pWXC7yXk*+(|;d7|A<@ve+K + * This choice is for demonstration purposes only. For a live experiment, + see [Choosing hardware for your qsim simulation](/qsim/choose_hw) * In the Boot disk section, click the Change button, and choose Container-Optimized OS. This overrides step 3 in the quickstart guide. * In the Firewall section, ensure that both the **Allow HTTP traffic** diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index 34725ce8..e575bb2a 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -34,7 +34,7 @@ for your project. ### Find out more - +* For a live experiment, see [Choosing hardware for your qsim simulation](/qsim/choose_hw) * [Choosing the right machine family and type](https://cloud.google.com/blog/products/compute/choose-the-right-google-compute-engine-machine-type-for-you) * [Creating a VM with attached GPUs](https://cloud.google.com/compute/docs/gpus/create-vm-with-gpus#create-new-gpu-vm) From 74b1a67cf921f8744a0c3aa3d3fcbc067ba19002 Mon Sep 17 00:00:00 2001 From: Ricardo Olenewa Date: Thu, 7 Oct 2021 17:22:11 +0000 Subject: [PATCH 119/246] Revisions based on internal review. --- docs/choose_hw.md | 18 +++++++++--------- docs/tutorials/gcp_gpu.md | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/choose_hw.md b/docs/choose_hw.md index db65950d..7ddfc76d 100644 --- a/docs/choose_hw.md +++ b/docs/choose_hw.md @@ -23,7 +23,7 @@ not technical limits. ## Choose hardware for your simulation -### 1. Evaluate whether your simulation can be run locally. +### 1. Evaluate whether your simulation can be run locally If you have a modern laptop with at least 8GB of memory, you can run your simulation locally in the following cases: @@ -60,7 +60,7 @@ benefits from high-bandwidth memory (above 100GB/s). The following chart shows the runtime for a circuit run on [Google Compute Engine](https://cloud.google.com/compute), using an NVidia A100 -processor, and a compute-optimized processor (c2-standard-4). Each circuit was +GPU, and a compute-optimized CPU (c2-standard-4). Each circuit was run with three different phase damping channel (p) settings: 0, 0.1, and 0.001. The graph is log scale. @@ -80,12 +80,12 @@ choose a specific machine: [Google Cloud pricing calculator](https://cloud.google.com/products/calculator). * Prioritizing performance is particularly important in the following scenarios: - * Simulating with an **higher f value** (f is the maximum number of + * Simulating with a **higher f value** (f is the maximum number of qubits allowed per fused gate). * For small to medium size circuits (up to 22 qubits), keep f low - (2 or 3) - * For medium to large size qubits (22+ qubits), use a higher f (3 - or 4) + (2 or 3). + * For medium to large size qubits (22+ qubits), use a higher f + typically, f=4 is the best option). * Simulating a **deep circuit** (depth 30+). ### 5. Consider multiple compute nodes @@ -144,9 +144,9 @@ depth beyond 20 qubits. * For circuits that contain fewer than 20 qubits, the qsimcirq translation layer performance overhead tends to dominate the runtime estimate. In addition to this, qsim is not optimized for small circuits - * ​​The total small circuits runtime overhead for an N qubit circuit - depends on the circuit depth and on N, and can be up to orders of - magnitude larger than $ 2^N $. + * The total small circuits runtime overhead for an N qubit circuit + depends on the circuit depth and on N. The overhead can be large enough to + conceal the $ 2^N $ growth in runtime. ## Sample benchmarks diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index e575bb2a..61e686fe 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -34,7 +34,7 @@ for your project. ### Find out more -* For a live experiment, see [Choosing hardware for your qsim simulation](/qsim/choose_hw) +* [Choosing hardware for your qsim simulation](/qsim/choose_hw) * [Choosing the right machine family and type](https://cloud.google.com/blog/products/compute/choose-the-right-google-compute-engine-machine-type-for-you) * [Creating a VM with attached GPUs](https://cloud.google.com/compute/docs/gpus/create-vm-with-gpus#create-new-gpu-vm) From 0e4e8c6ff66777c04b6c6024c34eac4f6c5592fa Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 7 Oct 2021 10:27:03 -0700 Subject: [PATCH 120/246] Address review comments. --- docs/tutorials/multinode/README.md | 235 +++++++++------------ docs/tutorials/multinode/terraform/init.sh | 10 +- 2 files changed, 108 insertions(+), 137 deletions(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index f7ab47e8..21e00f0e 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -1,85 +1,64 @@ # Multinode quantum simulation using HTCondor on GCP -This tutorial will take you through the process of running multiple simultaneous -`qsim` simulations on Google Cloud. In some situations, it is required to run -many instances of the same simulation. This could be used to provide a parameter -sweep or to evaluate noise characteristics. -## Objectives +In this tutorial, you will configure HTCondor to run multiple simulations of a +quantum circuit in parallel across multiple nodes. This method can be used to +accelerate Monte Carlo simulations of noisy quantum circuits. + +Objectives of this tutorial: * Use `terraform` to deploy a HTCondor cluster -* Run a multi-node simulation using HTCondor +* Run a multinode simulation using HTCondor * Query cluster information and monitor running jobs in HTCondor * Use `terraform` to destroy the cluster - -## Step 1: Create a project -If necessary, you can run this tutorial in an existing project, but to avoid collisions or damage to existing work, it is often better to create a new project. - -In this [GCP setup tutorial](https://quantumai.google/qsim/tutorials/qsimcirq_gcp#gcp_setup), the method to create Google Cloud project is explained in detail. - -When you have a project created, move on to the next step. - -> IMPORTANT: For this tutorial, it is best if you have `Owner` privilege on the project. If you do not have owner rights, the following roles will be required: -```bash -roles/compute.admin -roles/iam.serviceAccountUser -roles/monitoring.admin -roles/logging.admin -roles/autoscaling.metricsWrite -``` -Your Cloud admin will be able to provide these roles. - -## Step 2: Configure your environment +## 1. Configure your environment Although this tutorial can be run from your local computer, we recommend the use of [Google Cloud Shell](https://cloud.google.com/shell). Cloud Shell has many useful tools pre-installed. -Once you have created a project in the previous step, -the Cloud Console with Cloud Shell activeated can be reached through this link: (https://console.cloud.google.com/home/dashboard?cloudshell=true) +Once you have completed the [Before you begin](./gcp_before_you_begin.md) +tutorial, open the [Cloud Shell in the Cloud Console](https://console.cloud.google.com/home/dashboard?cloudshell=true). ### Clone this repo In your Cloud Shell window, clone this Github repo. ``` bash -git clone https://github.com/jrossthomson/qsim.git +git clone https://github.com/quantumlib/qsim.git ``` ### Change directory + Change directory to the tutorial: ``` bash cd qsim/docs/tutorials/multinode/terraform ``` This is where you will use `terraform` to create the HTCondor cluster required to run your jobs. -### Edit `init.sh` file to customize your environment +### Edit `init.sh` file to match your environment -You can now edit `init.sh` to change the name of the project you are using. You -can also change the zone and region as you see fit. More information is -available [here](https://cloud.google.com/compute/docs/regions-zones). +Using your favorite text file editor, open the `init.sh` file. The first few +lines should look like this: +```bash +# ---- Edit below -----# -Use your favorite text file editor, either the integrated [Cloud Shell -Editor](https://cloud.google.com/shell/docs/editor-overview), `vim`, `emacs` or `nano`. -For example: -``` bash -vim init.sh -``` -The file has many lines, but only edit the first 4. -``` bash -export TF_VAR_project=quantum-htcondor-15 -export TF_VAR_project_id=us-east4-c +export TF_VAR_project=[USER_PROJECT] export TF_VAR_zone=us-east4-c export TF_VAR_region=us-east4 ``` +Replace `[USER_PROJECT]` with the project ID you chose in the `Before you begin` +tutorial. -The most important is the first line, indicating the name of the project you created above. -``` bash -export TF_VAR_project=my-quantum-htcondor-project -``` -The project id needs to be globally unique and to be the same as the project you just created. +The other lines can optionally be modified to adjust your environment. +* The `TF_VAR_zone` and `TF_VAR_region` lines can be modified to select where +your project will create new jobs. + +#### Find out more + +* [Choosing a zone and region](https://cloud.google.com/compute/docs/regions-zones) ### Source the `init.sh` file -The edited `init.sh` file should be "sourced" in the cloud shell: +The edited `init.sh` file should be "sourced" in the cloud shell: ``` bash source init.sh ``` @@ -95,10 +74,10 @@ The final outcome of this script will include: This will take up to 60 seconds. At the end you will see output about permissions and the configuration of the account. -## Step 3: Run terraform +## 2. Run terraform -After the previous steps are completed, you can initialize `terraform` to begin your cluster creation. -The first step is to initialize the `terraform` state. +After the previous steps are completed, you can initialize `terraform` to begin +your cluster creation. The first step is to initialize the `terraform` state. ``` bash terraform init ``` @@ -106,9 +85,11 @@ A successful result will contain the text: ``` Terraform has been successfully initialized! ``` + ### Run the `make` command -For convenience, some terraform commands are prepared in a `Makefile`. This means -you can now create your cluster, with a simple `make` command. + +For convenience, some terraform commands are prepared in a `Makefile`. This +means you can now create your cluster, with a simple `make` command. ```bash make apply ``` @@ -116,26 +97,27 @@ A successful run will show: ``` Apply complete! Resources: 4 added, 0 changed, 0 destroyed. ``` -## Step4: Connect to the _submit_ node for HTCondor -Although there are ways to run `HTCondor` commands from your local machine, -the normal path is to login to the _submit_ node. From there you can run -commands to submit and monitor jobs on HTCondor. -### List VMs that were created by +## 3. Connect to the submit node for HTCondor -You can list the VMs created. One of them will be the submit node. It will be the VM with -"submit" in the name. +Although there are ways to run HTCondor commands from your local machine, +the normal path is to login to the submit node. From there you can run +commands to submit and monitor jobs on HTCondor. -``` +### List VMs that were created by HTCondor + +To see the VMs created by HTCondor, run: +```bash gcloud compute instances list ``` -You will see two instances listed: -* c-manager: the core controller node -* c-submit: the submit node where you will run interactive work. +At this point in the tutorial, you will see two instances listed: -Identify then log in to the `submit` node. If you used the standard install, the -cluster name is "c". In that case you would connect using the `gcloud ssh` -command. +* c-manager: the [central manager node](https://htcondor.readthedocs.io/en/latest/getting-htcondor/admin-quick-start.html#the-central-manager-role), +which manages resource requests behind the scenes +* c-submit: the [submit node](https://htcondor.readthedocs.io/en/latest/getting-htcondor/admin-quick-start.html#the-submit-role), +where you will submit jobs to be run in the cluster + +Identify and log into the submit node: ```bash gcloud compute ssh c-submit ``` @@ -145,12 +127,9 @@ something like: [mylogin@c-submit ~]$ ``` -### Checking the status -You can verify if the HTCondor install is completed: -``` -condor_q -``` -You will see output: +### Checking the status + +You can run `condor_q` to verify if the HTCondor install is completed. The output should look something like this: ``` -- Schedd: c-submit.c.quantum-htcondor-14.internal : <10.150.0.2:9618?... @ 08/18/21 18:37:50 OWNER BATCH_NAME SUBMITTED DONE RUN IDLE HOLD TOTAL JOB_IDS @@ -161,65 +140,51 @@ Total for all users: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, ``` If you get `command not found`, you will need to wait a few minutes for the HTCondor install to complete. -## Step 5: Get the sample code and run it -The HTCondor cluster is now ready for your jobs to be run. If you are familiar with HTCondor and you want to run your own jobs, you may do so. +## 4. Get the sample code and run it -If you don't have jobs to run, you can get sample jobs from this Github repo. You will clone the repo to -the submit node and run a job. +The HTCondor cluster is now ready for your jobs to be run. For this tutorial, +sample jobs have been provided in the Github repo. ### Clone the repo on your cluster -on the `submit` node you you can install the repo to get access to previously created submission files: -``` -git clone https://github.com/jrossthomson/qsim.git +On the submit node, you can install the repo to get access to previously +created submission files: +```bash +git clone https://github.com/quantumlib/qsim.git ``` Then cd to the tutorial directory. -``` +```bash cd qsim/docs/tutorials/multinode ``` + ### Submit a job + Now it is possible to submit a job: ``` condor_submit noiseless.sub ``` -If successful, the output will be: +This job will run the code in `noiseless3.py`, which executes a simple circuit and prints the results as a histogram. If successful, the output will be: ``` Submitting job(s). 1 job(s) submitted to cluster 1. ``` You can see the job in queue with the `condor_q` command. -``` -condor_q -``` + The job will take several minutes to finish. The time includes creating a VM -compute node, installing the HTCondor system and running the job. When completed -the files will be in the `out` directory. -``` -ls out -``` -will list the files: -``` -err.1-0 log.1-0 out.1-0 placeholder -``` -You can also see the progress of the job through the log file: -``` -tail -f out/log.1-0 -``` -After the job is completed, the output of the job can be seen: -``` -cat out/out.1-0 -``` -## Step5: Run multi-node noise simulations +compute node, installing the HTCondor system and running the job. When complete, the following files will be stored in the `out` directory: + +* `out/log.1-0` contains a progress log for the job as it executes. +* `out/out.1-0` contains the final output of the job. +* `out/err.1-0` contains any error reports. This should be empty. + +To view one of these files in the shell, you can run `cat [FILE]`, +replacing `[FILE]` with the name of the file to be viewed. + +## 5. Run multinode noise simulations + Noise simulations make use of a [Monte Carlo method](https://en.wikipedia.org/wiki/Monte_Carlo_method) for [quantum -trajectories](https://en.wikipedia.org/wiki/Quantum_Trajectory_Theory). - -The primary goal of these simulations is to understand the behavior of the -quantum circuit taking into account the effects of noise. There are many -potential sources of noise in a quantum circuit, including thermal noise, 1/f -noise or shot noise, as well as perturbations from cosmic rays. Understanding -and calibrating noise in quantum computing is an active field of investigation, -and it will require well characterized simulations. +trajectories](https://en.wikipedia.org/wiki/Quantum_Trajectory_Theory). To run multiple simulations, you can run the submit file `noise.sub`. The submit file is shown below. It is key to note that this is running a `docker` @@ -231,6 +196,13 @@ parameter ranges where circuits are particularly susceptible to noise. ### The noise.sub file + +To run multiple simulations, you can define a "submit file". `noise.sub` is +an example of this file format, and is shown below. Notable features include: + +* `universe = docker` means that all jobs will run inside a `docker` container. +* `queue 50` submits 50 separate copies of the job. + ``` universe = docker docker_image = gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest @@ -248,30 +220,24 @@ The job can be submitted with the `condor_submit` command. ``` condor_submit noise.sub ``` -The output will look as follows: +The output should look like this: ``` Submitting job(s).................................................. 50 job(s) submitted to cluster 2. ``` To monitor the ongoing process of jobs running, you can take advantage of the -Linux `watch` command and run `condor_q` repeatedly. When complete watch can -be exited with cntrl-c. +Linux `watch` command to run `condor_q` repeatedly: ``` watch "condor_q; condor_status" ``` The output of this command will show you the jobs in the queue as well as the VMs being created to run the jobs. There is a limit of 20 VMs for this -configuration of the cluster. This can be modified for future runs. - -When the command shows the queue is empty, the command can be stopped with cntrl-c. +configuration of the cluster. -If this is the second _submit_ you have run, you can see the output of all -the simulations. The output will be in the `out` directory. +When the queue is empty, the command can be stopped with CTRL-C. -``` -cat out/out.2-* -``` -You can see the results of the simulations. +The output from all trajectories will be stored in the `out` directory. To see +the results of the simulations, you can run `cat out/out.${TRAJECTORY_NUM}`, replacing `${TRAJECTORY_NUM}` with the trajectory number (e.g. `2-0`). ``` Counter({3: 462, 0: 452, 2: 50, 1: 36}) Counter({0: 475, 3: 435, 1: 49, 2: 41}) @@ -288,6 +254,11 @@ Counter({3: 466, 0: 442, 2: 51, 1: 41}) ## Next steps +This tutorial makes use of an experimental +[autoscaling script](./terraform/htcondor/autoscaler.py) to bring up and turn +down VMs as needed. After you finish, check the Compute Instances dashboard and +stop or delete any remaining VM(s) to prevent further billing. + The file being run in the previous example was `noise3.py`. To run your own simulations, simply create a new python file with your circuit and change the `noise3.py` references in `noise.sub` to point to the new file. @@ -295,15 +266,13 @@ simulations, simply create a new python file with your circuit and change the A detailed discussion of how to construct various types of noise in Cirq can be found [here](https://quantumai.google/cirq/noise). -## Final step: Shutting down - -> **IMPORTANT**: To avoid excess billing for this project, it is important to shut down the cluster. This is easily done using the make command built for this purpose. -``` -make destroy -``` - -> The most effective way to prevent further charges is to delete the project. -[The instructions are here.](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) - +For more information about managing your VMs, see the following documentation +from Google Cloud: +* [Stopping and starting a VM](https://cloud.google.com/compute/docs/instances/stop-start-instance) +* [Suspending and resuming an instance](https://cloud.google.com/compute/docs/instances/suspend-resume-instance) +* [Deleting a VM instance](https://cloud.google.com/compute/docs/instances/deleting-instance) +As an alternative to Google Cloud, you can download the Docker container or the +qsim source code to run quantum simulations on your own high-performance +computing platform. diff --git a/docs/tutorials/multinode/terraform/init.sh b/docs/tutorials/multinode/terraform/init.sh index c9d5d4bd..775ef679 100644 --- a/docs/tutorials/multinode/terraform/init.sh +++ b/docs/tutorials/multinode/terraform/init.sh @@ -1,11 +1,13 @@ # ---- Edit below -----# -export TF_VAR_project=quantum-htcondor-15 -export TF_VAR_project_id=us-east4-c +export TF_VAR_project=[USER_PROJECT] export TF_VAR_zone=us-east4-c export TF_VAR_region=us-east4 -export TF_VAR_multizone=true -export TF_VAR_numzones=4 # for regional/multizone, set to the number of regions in the zone + +export TF_VAR_multizone=false +# For regional/multizone, set this to the number of regions in the zone. +export TF_VAR_numzones=4 + # ---- Do not edit below -----# export TF_VAR_project_id=${TF_VAR_project} From 1881a9f3aff6908adb4501de492f3a16d1c8f778 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 7 Oct 2021 12:41:51 -0700 Subject: [PATCH 121/246] Fix bumps in run-through --- docs/tutorials/multinode/README.md | 58 +++++++++++++++++++----------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index 21e00f0e..a791c380 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -25,6 +25,8 @@ In your Cloud Shell window, clone this Github repo. ``` bash git clone https://github.com/quantumlib/qsim.git ``` +If you get an error saying something like `qsim already exists`, you may need +to delete the `qsim` directory with `rm -rf qsim` and rerun the clone command. ### Change directory @@ -117,10 +119,15 @@ which manages resource requests behind the scenes * c-submit: the [submit node](https://htcondor.readthedocs.io/en/latest/getting-htcondor/admin-quick-start.html#the-submit-role), where you will submit jobs to be run in the cluster -Identify and log into the submit node: -```bash -gcloud compute ssh c-submit -``` +### Connecting to the submit node + +To connect to the submit node, navigate to the +[Compute Instances](https://console.cloud.google.com/compute/instances) section +of the Cloud Console and press the `SSH` button next to the `c-submit` instance. +This will open a new window connected to the Submit node. You may see a prompt +asking to "disable Identity-Aware Proxy" during this step; simply accept the +prompt and the connection should complete. + Now you are logged in to your HTCondor cluster. You will see a command prompt something like: ```bash @@ -147,7 +154,7 @@ sample jobs have been provided in the Github repo. ### Clone the repo on your cluster -On the submit node, you can install the repo to get access to previously +On the submit node, you can clone the repo to get access to previously created submission files: ```bash git clone https://github.com/quantumlib/qsim.git @@ -186,18 +193,9 @@ Noise simulations make use of a [Monte Carlo method](https://en.wikipedia.org/wiki/Monte_Carlo_method) for [quantum trajectories](https://en.wikipedia.org/wiki/Quantum_Trajectory_Theory). -To run multiple simulations, you can run the submit file `noise.sub`. The -submit file is shown below. It is key to note that this is running a `docker` -container. Also, the `queue 50` command at the end of the file submits 50 -separate instances of the container. Each simulation will be different due to -the stochastic nature of noisy simulations. A typical analysis of the output -would be a statistical study of the means and variations, perhaps looking for -parameter ranges where circuits are particularly susceptible to noise. - - ### The noise.sub file -To run multiple simulations, you can define a "submit file". `noise.sub` is +To run multiple simulations, you can define a "submit" file. `noise.sub` is an example of this file format, and is shown below. Notable features include: * `universe = docker` means that all jobs will run inside a `docker` container. @@ -237,7 +235,11 @@ configuration of the cluster. When the queue is empty, the command can be stopped with CTRL-C. The output from all trajectories will be stored in the `out` directory. To see -the results of the simulations, you can run `cat out/out.${TRAJECTORY_NUM}`, replacing `${TRAJECTORY_NUM}` with the trajectory number (e.g. `2-0`). +the results of all simulations together, you can run: +``` +cat out/out.2-* +``` +The output should look something like this: ``` Counter({3: 462, 0: 452, 2: 50, 1: 36}) Counter({0: 475, 3: 435, 1: 49, 2: 41}) @@ -252,12 +254,28 @@ Counter({3: 466, 0: 442, 2: 51, 1: 41}) . ``` -## Next steps +## 6. Shutting down -This tutorial makes use of an experimental +> **IMPORTANT**: To avoid excess billing for this project, it is important to +shut down the cluster. If your Cloud Shell is still open, simply run: +``` +make destroy +``` +If your Cloud Shell closed at any point, you'll need to re-initialize it. +[Open a new shell](https://console.cloud.google.com/home/dashboard?cloudshell=true) +and run: +``` +cd qsim/docs/tutorials/multinode/terraform +source init.sh +make destroy +``` +After these commands complete, check the Compute Instances dashboard to verify +that all VMs have been shut down. This tutorial makes use of an experimental [autoscaling script](./terraform/htcondor/autoscaler.py) to bring up and turn -down VMs as needed. After you finish, check the Compute Instances dashboard and -stop or delete any remaining VM(s) to prevent further billing. +down VMs as needed. If any VMs remain after several minutes, you may need to +shut them down manually, as described in the next section. + +## Next steps The file being run in the previous example was `noise3.py`. To run your own simulations, simply create a new python file with your circuit and change the From 2812f2005c9ca1e94cbb75d54eab191472ca5a81 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 7 Oct 2021 12:44:26 -0700 Subject: [PATCH 122/246] unquote important section --- docs/tutorials/multinode/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index a791c380..a292e6d5 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -256,7 +256,7 @@ Counter({3: 466, 0: 442, 2: 51, 1: 41}) ## 6. Shutting down -> **IMPORTANT**: To avoid excess billing for this project, it is important to +**IMPORTANT**: To avoid excess billing for this project, it is important to shut down the cluster. If your Cloud Shell is still open, simply run: ``` make destroy From a6fdc2a73cfef2e45f595599aae8be4bcf208920 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 8 Oct 2021 08:26:09 -0700 Subject: [PATCH 123/246] Re-review --- docs/tutorials/multinode/README.md | 47 ++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index a292e6d5..94d1eb59 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -47,8 +47,8 @@ export TF_VAR_project=[USER_PROJECT] export TF_VAR_zone=us-east4-c export TF_VAR_region=us-east4 ``` -Replace `[USER_PROJECT]` with the project ID you chose in the `Before you begin` -tutorial. +Replace `[USER_PROJECT]` with the project name you chose on the +`Before you begin` page. The other lines can optionally be modified to adjust your environment. * The `TF_VAR_zone` and `TF_VAR_region` lines can be modified to select where @@ -113,26 +113,39 @@ To see the VMs created by HTCondor, run: gcloud compute instances list ``` At this point in the tutorial, you will see two instances listed: +``` +NAME: c-manager +ZONE: us-central1-a +MACHINE_TYPE: n1-standard-1 +PREEMPTIBLE: +INTERNAL_IP: 10.128.0.3 +EXTERNAL_IP: 104.198.206.37 +STATUS: RUNNING -* c-manager: the [central manager node](https://htcondor.readthedocs.io/en/latest/getting-htcondor/admin-quick-start.html#the-central-manager-role), -which manages resource requests behind the scenes -* c-submit: the [submit node](https://htcondor.readthedocs.io/en/latest/getting-htcondor/admin-quick-start.html#the-submit-role), -where you will submit jobs to be run in the cluster +NAME: c-submit +ZONE: us-central1-a +MACHINE_TYPE: n1-standard-1 +PREEMPTIBLE: +INTERNAL_IP: 10.128.0.2 +EXTERNAL_IP: 35.238.197.124 +STATUS: RUNNING +``` ### Connecting to the submit node -To connect to the submit node, navigate to the -[Compute Instances](https://console.cloud.google.com/compute/instances) section -of the Cloud Console and press the `SSH` button next to the `c-submit` instance. -This will open a new window connected to the Submit node. You may see a prompt -asking to "disable Identity-Aware Proxy" during this step; simply accept the -prompt and the connection should complete. +To connect to the submit node, click the `Compute Engine` item on the Cloud +dashboard. This will open the VM Instances page, where you should see the two +instances listed above. In the `c-submit` row, click on the `SSH` button to +open a new window connected to the submit node. You may see a prompt asking to +disable Identity-Aware Proxy during this step; simply accept the prompt and +the connection should complete. -Now you are logged in to your HTCondor cluster. You will see a command prompt -something like: +This new window is logged into your HTCondor cluster. You will see a command +prompt that looks something like this: ```bash [mylogin@c-submit ~]$ ``` +The following steps should be performed in this window. ### Checking the status @@ -184,7 +197,7 @@ compute node, installing the HTCondor system and running the job. When complete, * `out/out.1-0` contains the final output of the job. * `out/err.1-0` contains any error reports. This should be empty. -To view one of these files in the shell, you can run `cat [FILE]`, +To view one of these files in the shell, you can run `cat out/[FILE]`, replacing `[FILE]` with the name of the file to be viewed. ## 5. Run multinode noise simulations @@ -257,7 +270,9 @@ Counter({3: 466, 0: 442, 2: 51, 1: 41}) ## 6. Shutting down **IMPORTANT**: To avoid excess billing for this project, it is important to -shut down the cluster. If your Cloud Shell is still open, simply run: +shut down the cluster. Return to the Cloud dashboard window for the steps below. + +If your Cloud Shell is still open, simply run: ``` make destroy ``` From ec94311f095c027403e3c14dea84bf3a689ae9db Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 8 Oct 2021 09:15:11 -0700 Subject: [PATCH 124/246] Minor cleanups --- docs/tutorials/multinode/README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md index 94d1eb59..2cf434c7 100644 --- a/docs/tutorials/multinode/README.md +++ b/docs/tutorials/multinode/README.md @@ -118,16 +118,16 @@ NAME: c-manager ZONE: us-central1-a MACHINE_TYPE: n1-standard-1 PREEMPTIBLE: -INTERNAL_IP: 10.128.0.3 -EXTERNAL_IP: 104.198.206.37 +INTERNAL_IP: X.X.X.X +EXTERNAL_IP: X.X.X.X STATUS: RUNNING NAME: c-submit ZONE: us-central1-a MACHINE_TYPE: n1-standard-1 PREEMPTIBLE: -INTERNAL_IP: 10.128.0.2 -EXTERNAL_IP: 35.238.197.124 +INTERNAL_IP: X.X.X.X +EXTERNAL_IP: X.X.X.X STATUS: RUNNING ``` @@ -136,9 +136,10 @@ STATUS: RUNNING To connect to the submit node, click the `Compute Engine` item on the Cloud dashboard. This will open the VM Instances page, where you should see the two instances listed above. In the `c-submit` row, click on the `SSH` button to -open a new window connected to the submit node. You may see a prompt asking to -disable Identity-Aware Proxy during this step; simply accept the prompt and -the connection should complete. +open a new window connected to the submit node. During this step, you may see a +prompt that reads `Connection via Cloud Identity-Aware Proxy Failed`; simply +click on `Connect without Identity-Aware Proxy` and the connection should +complete. This new window is logged into your HTCondor cluster. You will see a command prompt that looks something like this: From b1aa2a08fc75813aebb6de13731ed1b12de42594 Mon Sep 17 00:00:00 2001 From: Antonio Sanchez Date: Mon, 11 Oct 2021 11:58:57 -0700 Subject: [PATCH 125/246] Replace Eigen::all with Eigen::indexing::all. A few placeholder symbols (including `last` and `all`) conflict with a lot of local variable names across many external projects that currently have `using namespace Eigen` statements. We (Eigen maintainers) decided to remove the `Eigen::*` aliases to avoid these collisions. --- lib/mps_simulator.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/mps_simulator.h b/lib/mps_simulator.h index 98d00310..8fbcbae1 100644 --- a/lib/mps_simulator.h +++ b/lib/mps_simulator.h @@ -220,7 +220,7 @@ class MPSSimulator final { block_0.fill(Complex(0, 0)); const auto keep_cols = (svd_u.cols() > bond_dim) ? bond_dim : svd_u.cols(); block_0.block(0, 0, svd_u.rows(), keep_cols).noalias() = - svd_u(Eigen::all, Eigen::seq(0, keep_cols - 1)); + svd_u(Eigen::indexing::all, Eigen::seq(0, keep_cols - 1)); // Place row product of S V into scratch to truncate and then B1. MatrixMap svd_v((Complex*)(raw_state + end), p, 2 * m_dim); @@ -233,7 +233,8 @@ class MPSSimulator final { for (unsigned i = 0; i < keep_rows; ++i) { svd_v.row(i) *= s_vector(i); } - block_1.block(0, 0, keep_rows, svd_v.cols()).noalias() = svd_v(row_seq, Eigen::all); + block_1.block(0, 0, keep_rows, svd_v.cols()).noalias() = + svd_v(row_seq, Eigen::indexing::all); } For for_; From 475cbe4de5551b43f8124d2dcda9f7adefc81b3c Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 12 Oct 2021 07:03:05 -0700 Subject: [PATCH 126/246] Fix broken example code --- docs/_index.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_index.yaml b/docs/_index.yaml index 064e2b70..093ca58b 100644 --- a/docs/_index.yaml +++ b/docs/_index.yaml @@ -58,9 +58,9 @@ landing_page: # Define a circuit to run # (Example is from the 2019 "Quantum Supremacy" experiement) - circuit = cirq.experiments. + circuit = (cirq.experiments. random_rotations_between_grid_interaction_layers_circuit( - qubits=qubits, depth=16) + qubits=qubits, depth=16)) # Measure qubits at the end of the circuit circuit.append(cirq.measure(*qubits, key='all_qubits')) From 153f7cf18eadb54a77dff52e7840836645bc4f2c Mon Sep 17 00:00:00 2001 From: Ricardo Olenewa Date: Tue, 12 Oct 2021 18:08:33 +0000 Subject: [PATCH 127/246] Updates to text and images. Moved multinode tutorial to better place. --- docs/choose_hw.md | 33 +- docs/images/qsim_runtime_comparison.png | Bin 201441 -> 0 bytes .../qsim_runtime_comparison_noiseless.png | Bin 0 -> 134499 bytes docs/images/qsim_runtime_comparison_noisy.png | Bin 0 -> 129429 bytes docs/tutorials/multinode.md | 312 ++++++++++++++++++ 5 files changed, 331 insertions(+), 14 deletions(-) delete mode 100644 docs/images/qsim_runtime_comparison.png create mode 100644 docs/images/qsim_runtime_comparison_noiseless.png create mode 100644 docs/images/qsim_runtime_comparison_noisy.png create mode 100644 docs/tutorials/multinode.md diff --git a/docs/choose_hw.md b/docs/choose_hw.md index 7ddfc76d..09dd9177 100644 --- a/docs/choose_hw.md +++ b/docs/choose_hw.md @@ -32,8 +32,8 @@ simulation locally in the following cases: * Noisy simulations that use fewer than 18 qubits. If you intend to simulate a circuit many times, consider multinode simulation. -For more information about multinode simulation -[see step 5, below](#5_consider_multiple_compute_nodes). +For more information about multinode simulation [see step 5, +below](#5_consider_multiple_compute_nodes). ### 2. Estimate your memory requirements @@ -53,18 +53,20 @@ benefits from high-bandwidth memory (above 100GB/s). faster) for circuits with more than 20 qubits. * The maximum number of qubits that you can simulate with a GPU is limited by the memory of the GPU. Currently, for a noiseless simulation on an NVIDIA - A100 GPU, the maximum number of qubits is 32. + A100 GPU (with 40GB of memory), the maximum number of qubits is 32. * For noiseless simulations with 32-40 qubits, you can use CPUs. However, the runtime time increases exponentially with the number of qubits, and runtimes are long for simulations above 32 qubits. -The following chart shows the runtime for a circuit run on +The following charts show the runtime for a random circuit run on [Google Compute Engine](https://cloud.google.com/compute), using an NVidia A100 -GPU, and a compute-optimized CPU (c2-standard-4). Each circuit was -run with three different phase damping channel (p) settings: 0, 0.1, and 0.001. -The graph is log scale. +GPU, and a compute-optimized CPU (c2-standard-4). The first chart shows the +runtimes for the noiseless simulation. The second chart shows the runtimes for a +noisy simulation, using a phase damping channel (p=0.01). The charts use a log +scale. -![qsim runtime comparison on multipe processors and configurations](images/qsim_runtime_comparison.png) +![qsim runtime comparison on multipe processors: noiseless](images/qsim_runtime_comparison_noiseless.png) +![qsim runtime comparison on multipe processors: noisy](images/qsim_runtime_comparison_noisy.png) ### 4. Select a specific machine @@ -96,6 +98,9 @@ are “embarrassingly parallelizable”, there is an automated workflow for distributing these trajectories over multiple nodes. A simulation of many noiseless circuits can also be distributed over multiple compute nodes. +For mor information about running a mulitnode simulation, see [Multinode quantum +simulation using HTCondor on Google Cloud](/qsim/tutorials/multinode). + ## Runtime estimates Runtime grows exponentially with the number of qubits, and linearly with circuit @@ -104,7 +109,7 @@ depth beyond 20 qubits. * For noiseless simulations, runtime grows at a rate of $ 2^N $ for an N-qubit circuit. For more information about runtimes for small circuits, see [Additional notes for advanced users](#additional_notes_for_advanced_users) - below), + below). * For noisy simulations, runtime grows at a rate of $ 2^N $ multiplied by the number of iterations for an N-qubit circuit. @@ -113,16 +118,16 @@ depth beyond 20 qubits. * The impact of noise on simulation depends on: * What type of errors are included in your noise channel (decoherence, depolarizing channels, coherent errors, readout errors). - * How you can represent your noise model using Kraus operator formalism + * How you can represent your noise model using Kraus operator formalism: * Performance is best in the case where all Kraus operators are proportional to unitary matrices, such as when using only a depolarizing channel. In this case, memory requirements are equal to - noiseless memory requirements (8\*2^n bytes) + noiseless memory requirements (8\*2^n bytes). * Using noise which cannot be represented with Kraus operators proportional to unitary matrices, can slow down simulations by a factor of up to 6** **compared to using a depolarizing channel only * Noisy simulations are faster with lower noise (when one Kraus - operator dominates) + operator dominates). * Experimenting with the 'f' parameter (maximum number of qubits allowed per fused gate): * The advanced user is advised to try out multiple f values to optimize @@ -130,7 +135,7 @@ depth beyond 20 qubits. * Note that f=2 or f=3 can be optimal for large circuits simulated on CPUs with a smaller number of threads (say, up to four or eight threads). However, this depends on the circuit structure. - * Note that f=6 is very rarely optimal + * Note that f=6 is very rarely optimal. * Using the optimal number of threads: * Use the maximum number of threads on CPUs for the best performance. * If the maximum number of threads is not used on multi-socket machines @@ -143,7 +148,7 @@ depth beyond 20 qubits. * Runtime estimates for small circuits: * For circuits that contain fewer than 20 qubits, the qsimcirq translation layer performance overhead tends to dominate the runtime estimate. In - addition to this, qsim is not optimized for small circuits + addition to this, qsim is not optimized for small circuits. * The total small circuits runtime overhead for an N qubit circuit depends on the circuit depth and on N. The overhead can be large enough to conceal the $ 2^N $ growth in runtime. diff --git a/docs/images/qsim_runtime_comparison.png b/docs/images/qsim_runtime_comparison.png deleted file mode 100644 index ac6030742964fdbc92c091b3a28f40e07975d49e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 201441 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 I#<{Ei1AGv}1poj5 literal 0 HcmV?d00001 diff --git a/docs/images/qsim_runtime_comparison_noisy.png b/docs/images/qsim_runtime_comparison_noisy.png new file mode 100644 index 0000000000000000000000000000000000000000..ae73659212bf4fcfe2b22c30a1e4856e712b2082 GIT binary patch literal 129429 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

@7fI%sUBIM#HhZV^zYi92E3F$Ifv(O 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 Date: Tue, 12 Oct 2021 19:29:01 +0000 Subject: [PATCH 128/246] Remove duplicate content from multinode tutorial. --- docs/tutorials/multinode/README.md | 312 ----------------------------- 1 file changed, 312 deletions(-) delete mode 100644 docs/tutorials/multinode/README.md diff --git a/docs/tutorials/multinode/README.md b/docs/tutorials/multinode/README.md deleted file mode 100644 index 2cf434c7..00000000 --- a/docs/tutorials/multinode/README.md +++ /dev/null @@ -1,312 +0,0 @@ -# Multinode quantum simulation using HTCondor on GCP - -In this tutorial, you will configure HTCondor to run multiple simulations of a -quantum circuit in parallel across multiple nodes. This method can be used to -accelerate Monte Carlo simulations of noisy quantum circuits. - -Objectives of this tutorial: - -* Use `terraform` to deploy a HTCondor cluster -* Run a multinode simulation using HTCondor -* Query cluster information and monitor running jobs in HTCondor -* Use `terraform` to destroy the cluster - -## 1. Configure your environment - -Although this tutorial can be run from your local computer, we recommend the use -of [Google Cloud Shell](https://cloud.google.com/shell). Cloud Shell has many useful tools pre-installed. - -Once you have completed the [Before you begin](./gcp_before_you_begin.md) -tutorial, open the [Cloud Shell in the Cloud Console](https://console.cloud.google.com/home/dashboard?cloudshell=true). - -### Clone this repo - -In your Cloud Shell window, clone this Github repo. -``` bash -git clone https://github.com/quantumlib/qsim.git -``` -If you get an error saying something like `qsim already exists`, you may need -to delete the `qsim` directory with `rm -rf qsim` and rerun the clone command. - -### Change directory - -Change directory to the tutorial: -``` bash -cd qsim/docs/tutorials/multinode/terraform -``` -This is where you will use `terraform` to create the HTCondor cluster required to run your jobs. - -### Edit `init.sh` file to match your environment - -Using your favorite text file editor, open the `init.sh` file. The first few -lines should look like this: -```bash -# ---- Edit below -----# - -export TF_VAR_project=[USER_PROJECT] -export TF_VAR_zone=us-east4-c -export TF_VAR_region=us-east4 -``` -Replace `[USER_PROJECT]` with the project name you chose on the -`Before you begin` page. - -The other lines can optionally be modified to adjust your environment. -* The `TF_VAR_zone` and `TF_VAR_region` lines can be modified to select where -your project will create new jobs. - -#### Find out more - -* [Choosing a zone and region](https://cloud.google.com/compute/docs/regions-zones) - -### Source the `init.sh` file - -The edited `init.sh` file should be "sourced" in the cloud shell: -``` bash -source init.sh -``` -Respond `Agree` to any pop-ups that request permissions on the Google Cloud platform. - -The final outcome of this script will include: - -* A gcloud config setup correctly -* A service account created -* The appropriate permissions assigned to the service account -* A key file created to enable the use of Google Cloud automation. - -This will take up to 60 seconds. At the end you will see output about -permissions and the configuration of the account. - -## 2. Run terraform - -After the previous steps are completed, you can initialize `terraform` to begin -your cluster creation. The first step is to initialize the `terraform` state. -``` bash -terraform init -``` -A successful result will contain the text: -``` -Terraform has been successfully initialized! -``` - -### Run the `make` command - -For convenience, some terraform commands are prepared in a `Makefile`. This -means you can now create your cluster, with a simple `make` command. -```bash -make apply -``` -A successful run will show: -``` -Apply complete! Resources: 4 added, 0 changed, 0 destroyed. -``` - -## 3. Connect to the submit node for HTCondor - -Although there are ways to run HTCondor commands from your local machine, -the normal path is to login to the submit node. From there you can run -commands to submit and monitor jobs on HTCondor. - -### List VMs that were created by HTCondor - -To see the VMs created by HTCondor, run: -```bash -gcloud compute instances list -``` -At this point in the tutorial, you will see two instances listed: -``` -NAME: c-manager -ZONE: us-central1-a -MACHINE_TYPE: n1-standard-1 -PREEMPTIBLE: -INTERNAL_IP: X.X.X.X -EXTERNAL_IP: X.X.X.X -STATUS: RUNNING - -NAME: c-submit -ZONE: us-central1-a -MACHINE_TYPE: n1-standard-1 -PREEMPTIBLE: -INTERNAL_IP: X.X.X.X -EXTERNAL_IP: X.X.X.X -STATUS: RUNNING -``` - -### Connecting to the submit node - -To connect to the submit node, click the `Compute Engine` item on the Cloud -dashboard. This will open the VM Instances page, where you should see the two -instances listed above. In the `c-submit` row, click on the `SSH` button to -open a new window connected to the submit node. During this step, you may see a -prompt that reads `Connection via Cloud Identity-Aware Proxy Failed`; simply -click on `Connect without Identity-Aware Proxy` and the connection should -complete. - -This new window is logged into your HTCondor cluster. You will see a command -prompt that looks something like this: -```bash -[mylogin@c-submit ~]$ -``` -The following steps should be performed in this window. - -### Checking the status - -You can run `condor_q` to verify if the HTCondor install is completed. The output should look something like this: -``` --- Schedd: c-submit.c.quantum-htcondor-14.internal : <10.150.0.2:9618?... @ 08/18/21 18:37:50 -OWNER BATCH_NAME SUBMITTED DONE RUN IDLE HOLD TOTAL JOB_IDS - -Total for query: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended -Total for drj: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended -Total for all users: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended -``` -If you get `command not found`, you will need to wait a few minutes for the HTCondor install to complete. - -## 4. Get the sample code and run it - -The HTCondor cluster is now ready for your jobs to be run. For this tutorial, -sample jobs have been provided in the Github repo. - -### Clone the repo on your cluster - -On the submit node, you can clone the repo to get access to previously -created submission files: -```bash -git clone https://github.com/quantumlib/qsim.git -``` -Then cd to the tutorial directory. -```bash -cd qsim/docs/tutorials/multinode -``` - -### Submit a job - -Now it is possible to submit a job: -``` -condor_submit noiseless.sub -``` -This job will run the code in `noiseless3.py`, which executes a simple circuit and prints the results as a histogram. If successful, the output will be: -``` -Submitting job(s). -1 job(s) submitted to cluster 1. -``` -You can see the job in queue with the `condor_q` command. - -The job will take several minutes to finish. The time includes creating a VM -compute node, installing the HTCondor system and running the job. When complete, the following files will be stored in the `out` directory: - -* `out/log.1-0` contains a progress log for the job as it executes. -* `out/out.1-0` contains the final output of the job. -* `out/err.1-0` contains any error reports. This should be empty. - -To view one of these files in the shell, you can run `cat out/[FILE]`, -replacing `[FILE]` with the name of the file to be viewed. - -## 5. Run multinode noise simulations - -Noise simulations make use of a [Monte Carlo -method](https://en.wikipedia.org/wiki/Monte_Carlo_method) for [quantum -trajectories](https://en.wikipedia.org/wiki/Quantum_Trajectory_Theory). - -### The noise.sub file - -To run multiple simulations, you can define a "submit" file. `noise.sub` is -an example of this file format, and is shown below. Notable features include: - -* `universe = docker` means that all jobs will run inside a `docker` container. -* `queue 50` submits 50 separate copies of the job. - -``` -universe = docker -docker_image = gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest -arguments = python3 noise3.py -should_transfer_files = YES -transfer_input_files = noise3.py -when_to_transfer_output = ON_EXIT -output = out/out.$(Cluster)-$(Process) -error = out/err.$(Cluster)-$(Process) -log = out/log.$(Cluster)-$(Process) -request_memory = 10GB -queue 50 -``` -The job can be submitted with the `condor_submit` command. -``` -condor_submit noise.sub -``` -The output should look like this: -``` -Submitting job(s).................................................. -50 job(s) submitted to cluster 2. -``` -To monitor the ongoing process of jobs running, you can take advantage of the -Linux `watch` command to run `condor_q` repeatedly: -``` -watch "condor_q; condor_status" -``` -The output of this command will show you the jobs in the queue as well as the -VMs being created to run the jobs. There is a limit of 20 VMs for this -configuration of the cluster. - -When the queue is empty, the command can be stopped with CTRL-C. - -The output from all trajectories will be stored in the `out` directory. To see -the results of all simulations together, you can run: -``` -cat out/out.2-* -``` -The output should look something like this: -``` -Counter({3: 462, 0: 452, 2: 50, 1: 36}) -Counter({0: 475, 3: 435, 1: 49, 2: 41}) -Counter({0: 450, 3: 440, 1: 59, 2: 51}) -Counter({0: 459, 3: 453, 2: 51, 1: 37}) -Counter({3: 471, 0: 450, 2: 46, 1: 33}) -Counter({3: 467, 0: 441, 1: 54, 2: 38}) -Counter({3: 455, 0: 455, 1: 50, 2: 40}) -Counter({3: 466, 0: 442, 2: 51, 1: 41}) -. -. -. -``` - -## 6. Shutting down - -**IMPORTANT**: To avoid excess billing for this project, it is important to -shut down the cluster. Return to the Cloud dashboard window for the steps below. - -If your Cloud Shell is still open, simply run: -``` -make destroy -``` -If your Cloud Shell closed at any point, you'll need to re-initialize it. -[Open a new shell](https://console.cloud.google.com/home/dashboard?cloudshell=true) -and run: -``` -cd qsim/docs/tutorials/multinode/terraform -source init.sh -make destroy -``` -After these commands complete, check the Compute Instances dashboard to verify -that all VMs have been shut down. This tutorial makes use of an experimental -[autoscaling script](./terraform/htcondor/autoscaler.py) to bring up and turn -down VMs as needed. If any VMs remain after several minutes, you may need to -shut them down manually, as described in the next section. - -## Next steps - -The file being run in the previous example was `noise3.py`. To run your own -simulations, simply create a new python file with your circuit and change the -`noise3.py` references in `noise.sub` to point to the new file. - -A detailed discussion of how to construct various types of noise in Cirq can be -found [here](https://quantumai.google/cirq/noise). - -For more information about managing your VMs, see the following documentation -from Google Cloud: - -* [Stopping and starting a VM](https://cloud.google.com/compute/docs/instances/stop-start-instance) -* [Suspending and resuming an instance](https://cloud.google.com/compute/docs/instances/suspend-resume-instance) -* [Deleting a VM instance](https://cloud.google.com/compute/docs/instances/deleting-instance) - -As an alternative to Google Cloud, you can download the Docker container or the -qsim source code to run quantum simulations on your own high-performance -computing platform. From 339106842b7a45bab51f42eacb749ae7f4a9e46d Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 13 Oct 2021 13:57:37 -0700 Subject: [PATCH 129/246] Update to match Cirq v0.13 --- qsimcirq/qsim_circuit.py | 4 ++-- qsimcirq_tests/qsimcirq_test.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/qsimcirq/qsim_circuit.py b/qsimcirq/qsim_circuit.py index 4d63fcd6..a3089752 100644 --- a/qsimcirq/qsim_circuit.py +++ b/qsimcirq/qsim_circuit.py @@ -376,7 +376,7 @@ def to_matrix(op: cirq.ops.GateOperation): ops_by_mix.append(qsim_op) moment_length = max(moment_length, 1) pass - elif cirq.has_channel(qsim_op): + elif cirq.has_kraus(qsim_op): ops_by_channel.append(qsim_op) moment_length = max(moment_length, 1) pass @@ -407,7 +407,7 @@ def to_matrix(op: cirq.ops.GateOperation): # Handle channel output. for channel in ops_by_channel: chdata = [] - for i, mat in enumerate(cirq.channel(channel)): + for i, mat in enumerate(cirq.kraus(channel)): square_mat = np.reshape(mat, (int(np.sqrt(mat.size)), -1)) unitary = cirq.is_unitary(square_mat) singular_vals = np.linalg.svd(square_mat)[1] diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index b6acd09a..aa63846e 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -29,7 +29,7 @@ class NoiseTrigger(cirq.SingleQubitGate): # def _mixture_(self): # return ((1.0, np.asarray([1, 0, 0, 1])),) - def _channel_(self): + def _kraus_(self): return (np.asarray([1, 0, 0, 1]),) @@ -781,8 +781,8 @@ def test_mixture_simulation(): possible_circuits = [ cirq.Circuit(cirq.X(q0) ** 0.5, cirq.X(q1) ** 0.5, pf, bf) # Extract the operators from the mixtures to construct trajectories. - for pf in [NoiseStep(m).on(q0) for m in cirq.channel(pflip)] - for bf in [NoiseStep(m).on(q1) for m in cirq.channel(bflip)] + for pf in [NoiseStep(m).on(q0) for m in cirq.kraus(pflip)] + for bf in [NoiseStep(m).on(q1) for m in cirq.kraus(bflip)] ] possible_states = [ cirq.Simulator().simulate(pc).state_vector() for pc in possible_circuits @@ -823,8 +823,8 @@ def test_channel_simulation(): possible_circuits = [ cirq.Circuit(cirq.X(q0) ** 0.5, cirq.X(q1) ** 0.5, ad, gad) # Extract the operators from the channels to construct trajectories. - for ad in [NoiseStep(m).on(q0) for m in cirq.channel(amp_damp)] - for gad in [NoiseStep(m).on(q1) for m in cirq.channel(gen_amp_damp)] + for ad in [NoiseStep(m).on(q0) for m in cirq.kraus(amp_damp)] + for gad in [NoiseStep(m).on(q1) for m in cirq.kraus(gen_amp_damp)] ] possible_states = [ cirq.Simulator().simulate(pc).state_vector() for pc in possible_circuits @@ -860,7 +860,7 @@ def __init__(self, *prob_mat_pairs, num_qubits=1): def _num_qubits_(self): return self._num_qubits - def _channel_(self): + def _kraus_(self): return [cirq.unitary(op) for _, op, in self._prob_op_pairs] def steps(self): From af6e198b286d4046ca0e09ef6169ab0ace65e860 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 18 Oct 2021 07:49:29 -0700 Subject: [PATCH 130/246] Remove floating backtick --- docs/tutorials/gcp_gpu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index 61e686fe..2f28f791 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -72,7 +72,7 @@ machine to your virtual machine. 2. Install the CUDA toolkit. ```shell - sudo apt install -y nvidia-cuda-toolkit` + sudo apt install -y nvidia-cuda-toolkit ``` 3. Add your CUDA toolkit to the environment search path. From b96fc9ed83fbf7af578f59e9889560a93455a3f3 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 18 Oct 2021 07:54:37 -0700 Subject: [PATCH 131/246] virutal -> virtual --- docs/tutorials/gcp_gpu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index 2f28f791..5cf736be 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -61,7 +61,7 @@ Use SSH in the `glcoud` tool to communicate with your VM. When the command completes successfully, your prompt changes from your local machine to your virtual machine. -## 3. Enable your virutal machine to use the GPU +## 3. Enable your virtual machine to use the GPU 1. Install the GPU driver. In the Google Cloud documentation, in the Installing GPU drivers guide, follow the steps provided in the following sections: From 7dbf6cd7c8e5b65e887a6d1d6f50a6d28bb1a08e Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Thu, 21 Oct 2021 17:48:25 +0200 Subject: [PATCH 132/246] Split gate fusion and simulation. --- docs/usage.md | 12 ++-- lib/expect.h | 4 +- lib/fuser.h | 38 ++++++++--- lib/fuser_basic.h | 60 +++++++++++------- lib/fuser_mqubit.h | 67 ++++++++++++-------- lib/gate_appl.h | 11 ++-- lib/hybrid.h | 9 ++- lib/run_qsim.h | 49 +++++++++++--- tests/fuser_mqubit_test.cc | 88 +++++++++++++------------- tests/mps_simulator_test.cc | 9 ++- tests/simulator_testfixture.h | 16 +++-- tests/statespace_testfixture.h | 28 ++++---- tests/unitary_calculator_testfixture.h | 4 +- 13 files changed, 247 insertions(+), 148 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index a0992fe2..14dbcda8 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -22,7 +22,7 @@ Sample circuits are provided in |`-d maxtime` | maximum time | |`-t num_threads` | number of threads to use| |`-f max_fused_size` | maximum fused gate size| -|`-v verbosity` | verbosity level (0,1,>1)| +|`-v verbosity` | verbosity level (0,1,2,3,4,5)| |`-z` | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsim_base computes all the amplitudes and just prints the first eight of them @@ -46,7 +46,7 @@ Example: |`-d maxtime` | maximum time | |`-t num_threads` | number of threads to use| |`-f max_fused_size` | maximum fused gate size| -|`-v verbosity` | verbosity level (0,1,>1)| +|`-v verbosity` | verbosity level (0,1,2,3,4,5)| |`-z` | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsim_von_neumann computes all the amplitudes and calculates the von Neumann @@ -77,7 +77,7 @@ Example: |`-o output_files` | comma-separated list of amplitude output files| |`-t num_threads` | number of threads to use| |`-f max_fused_size` | maximum fused gate size| -|`-v verbosity` | verbosity level (0,1,>1)| +|`-v verbosity` | verbosity level (0,1,2,3,4,5)| |`-z` | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsim_amplitudes reads input files of bitstrings, computes the corresponding @@ -112,7 +112,7 @@ Example: |`-t traj0` | starting trajectory | |`-n num_trajectories ` | number of trajectories to run starting with `traj0` | |`-f max_fused_size` | maximum fused gate size| -|`-v verbosity` | verbosity level (0,1)| +|`-v verbosity` | verbosity level (0,1,2,3,4,5)| qsim_qtrajectory_cuda runs on GPUs. qsim_qtrajectory_cuda performs quantum trajactory simulations with amplitude damping and phase damping noise channels. @@ -145,7 +145,7 @@ Example: |`-p num_prefix_gates` | number of prefix gates| |`-r num_root_gates` | number of root gates| |`-t num_threads` | number of threads to use| -|`-v verbosity` | verbosity level (0,>0)| +|`-v verbosity` | verbosity level (0,1,4,5)| |`-z` | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsimh_base just computes and just prints the first eight amplitudes. The hybrid @@ -226,7 +226,7 @@ maximum "time". |`-i input_file` | bitstring input file| |`-o output_file` | amplitude output file| |`-t num_threads` | number of threads to use| -|`-v verbosity` | verbosity level (0,>0)| +|`-v verbosity` | verbosity level (0,1,4,5)| |`-z` | set flush-to-zero and denormals-are-zeros MXCSR control flags| qsimh_amplitudes reads the input file of bitstrings, computes the corresponding diff --git a/lib/expect.h b/lib/expect.h index 2943c713..66ff817d 100644 --- a/lib/expect.h +++ b/lib/expect.h @@ -123,8 +123,8 @@ std::complex ExpectationValue( break; } - auto matrix = CalculateFusedMatrix(fgate); - auto r = simulator.ExpectationValue(fgate.qubits, matrix.data(), state); + auto r = simulator.ExpectationValue( + fgate.qubits, fgate.matrix.data(), state); eval += str.weight * r; } } diff --git a/lib/fuser.h b/lib/fuser.h index 927349f1..93933975 100644 --- a/lib/fuser.h +++ b/lib/fuser.h @@ -50,6 +50,10 @@ struct GateFused { * Ordered list of component gates. */ std::vector gates; + /** + * Fused gate matrix. + */ + Matrix matrix; }; /** @@ -134,16 +138,14 @@ class Fuser { /** * Multiplies component gate matrices of a fused gate. * @param gate Fused gate. - * @return Matrix product of component matrices. */ -template -inline Matrix CalculateFusedMatrix(const FusedGate& gate) { - Matrix matrix; - MatrixIdentity(unsigned{1} << gate.qubits.size(), matrix); +template +inline void CalculateFusedMatrix(FusedGate& gate) { + MatrixIdentity(unsigned{1} << gate.qubits.size(), gate.matrix); for (auto pgate : gate.gates) { if (gate.qubits.size() == pgate->qubits.size()) { - MatrixMultiply(gate.qubits.size(), pgate->matrix, matrix); + MatrixMultiply(gate.qubits.size(), pgate->matrix, gate.matrix); } else { unsigned mask = 0; @@ -157,11 +159,31 @@ inline Matrix CalculateFusedMatrix(const FusedGate& gate) { } MatrixMultiply(mask, pgate->qubits.size(), pgate->matrix, - gate.qubits.size(), matrix); + gate.qubits.size(), gate.matrix); } } +} - return matrix; +/** + * Multiplies component gate matrices for a range of fused gates. + * @param gbeg, gend The iterator range [gbeg, gend) of fused gates. + */ +template +inline void CalculateFusedMatrices(Iterator gbeg, Iterator gend) { + for (auto g = gbeg; g != gend; ++g) { + if (g->kind != gate::kMeasurement) { + CalculateFusedMatrix(*g); + } + } +} + +/** + * Multiplies component gate matrices for a vector of fused gates. + * @param gates The vector of fused gates. + */ +template +inline void CalculateFusedMatrices(std::vector& gates) { + CalculateFusedMatrices(gates.begin(), gates.end()); } } // namespace qsim diff --git a/lib/fuser_basic.h b/lib/fuser_basic.h index 345db25a..abc006e8 100644 --- a/lib/fuser_basic.h +++ b/lib/fuser_basic.h @@ -53,29 +53,29 @@ class BasicGateFuser final : public Fuser { /** * Stores sets of gates that can be applied together. Only one- and - * two-qubit gates will get fused. Gates fused with this method are not - * multiplied together until ApplyFusedGate is called on the output. - * To respect specific time boundaries while fusing gates, use the other - * version of this method below. + * two-qubit gates will get fused. To respect specific time boundaries while + * fusing gates, use the other version of this method below. * @param param Options for gate fusion. * @param num_qubits The number of qubits acted on by 'gates'. * @param gates The gates (or pointers to the gates) to be fused. * Gate times of the gates that act on the same qubits should be ordered. * Gates that are out of time order should not cross the time boundaries * set by measurement gates. + * @param fuse_matrix If true, multiply gate matrices together. * @return A vector of fused gate objects. Each element is a set of gates * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates(const Parameter& param, unsigned num_qubits, - const std::vector& gates) { - return FuseGates(param, num_qubits, gates.cbegin(), gates.cend(), {}); + const std::vector& gates, + bool fuse_matrix = true) { + return FuseGates( + param, num_qubits, gates.cbegin(), gates.cend(), {}, fuse_matrix); } /** * Stores sets of gates that can be applied together. Only one- and - * two-qubit gates will get fused. Gates fused with this method are not - * multiplied together until ApplyFusedGate is called on the output. + * two-qubit gates will get fused. * @param param Options for gate fusion. * @param num_qubits The number of qubits acted on by 'gates'. * @param gates The gates (or pointers to the gates) to be fused. @@ -85,43 +85,44 @@ class BasicGateFuser final : public Fuser { * @param times_to_split_at Ordered list of time steps (boundaries) at which * to separate fused gates. Each element of the output will contain gates * from a single 'window' in this list. + * @param fuse_matrix If true, multiply gate matrices together. * @return A vector of fused gate objects. Each element is a set of gates * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates( const Parameter& param, unsigned num_qubits, const std::vector& gates, - const std::vector& times_to_split_at) { + const std::vector& times_to_split_at, + bool fuse_matrix = true) { return FuseGates(param, num_qubits, gates.cbegin(), gates.cend(), - times_to_split_at); + times_to_split_at, fuse_matrix); } /** * Stores sets of gates that can be applied together. Only one- and - * two-qubit gates will get fused. Gates fused with this method are not - * multiplied together until ApplyFusedGate is called on the output. - * To respect specific time boundaries while fusing gates, use the other - * version of this method below. + * two-qubit gates will get fused. To respect specific time boundaries while + * fusing gates, use the other version of this method below. * @param param Options for gate fusion. * @param num_qubits The number of qubits acted on by gates. * @param gfirst, glast The iterator range [gfirst, glast) to fuse gates * (or pointers to gates) in. Gate times of the gates that act on the same * qubits should be ordered. Gates that are out of time order should not * cross the time boundaries set by measurement gates. + * @param fuse_matrix If true, multiply gate matrices together. * @return A vector of fused gate objects. Each element is a set of gates * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates( const Parameter& param, unsigned num_qubits, typename std::vector::const_iterator gfirst, - typename std::vector::const_iterator glast) { - return FuseGates(param, num_qubits, gfirst, glast, {}); + typename std::vector::const_iterator glast, + bool fuse_matrix = true) { + return FuseGates(param, num_qubits, gfirst, glast, {}, fuse_matrix); } /** * Stores sets of gates that can be applied together. Only one- and - * two-qubit gates will get fused. Gates fused with this method are not - * multiplied together until ApplyFusedGate is called on the output. + * two-qubit gates will get fused. * @param param Options for gate fusion. * @param num_qubits The number of qubits acted on by gates. * @param gfirst, glast The iterator range [gfirst, glast) to fuse gates @@ -132,6 +133,7 @@ class BasicGateFuser final : public Fuser { * @param times_to_split_at Ordered list of time steps (boundaries) at which * to separate fused gates. Each element of the output will contain gates * from a single 'window' in this list. + * @param fuse_matrix If true, multiply gate matrices together. * @return A vector of fused gate objects. Each element is a set of gates * acting on a specific pair of qubits which can be applied as a group. */ @@ -139,7 +141,8 @@ class BasicGateFuser final : public Fuser { const Parameter& param, unsigned num_qubits, typename std::vector::const_iterator gfirst, typename std::vector::const_iterator glast, - const std::vector& times_to_split_at) { + const std::vector& times_to_split_at, + bool fuse_matrix = true) { std::vector gates_fused; if (gfirst >= glast) return gates_fused; @@ -243,11 +246,11 @@ class BasicGateFuser final : public Fuser { } gates_fused.push_back({pgate->kind, pgate->time, pgate->qubits, - pgate, {pgate}}); + pgate, {pgate}, {}}); } else if (pgate->qubits.size() == 1) { unsigned q0 = pgate->qubits[0]; - GateFused gate_f = {pgate->kind, pgate->time, {q0}, pgate, {}}; + GateFused gate_f = {pgate->kind, pgate->time, {q0}, pgate, {}, {}}; last[q0] = Advance(last[q0], gates_lat[q0], gate_f.gates); gate_f.gates.push_back(gates_lat[q0][last[q0]]); @@ -260,7 +263,8 @@ class BasicGateFuser final : public Fuser { if (Done(last[q0], pgate->time, gates_lat[q0])) continue; - GateFused gate_f = {pgate->kind, pgate->time, {q0, q1}, pgate, {}}; + GateFused gate_f = + {pgate->kind, pgate->time, {q0, q1}, pgate, {}, {}}; do { last[q0] = Advance(last[q0], gates_lat[q0], gate_f.gates); @@ -290,7 +294,7 @@ class BasicGateFuser final : public Fuser { const auto& mea_gates_at_time = measurement_gates[pgate->time]; - GateFused gate_f = {pgate->kind, pgate->time, {}, pgate, {}}; + GateFused gate_f = {pgate->kind, pgate->time, {}, pgate, {}, {}}; gate_f.gates.reserve(mea_gates_at_time.size()); // Fuse measurement gates with equal times. @@ -307,6 +311,14 @@ class BasicGateFuser final : public Fuser { if (gate_it == glast) break; } + if (fuse_matrix) { + for (auto& gate_f : gates_fused) { + if (gate_f.kind != gate::kMeasurement && gate_f.kind != gate::kDecomp) { + CalculateFusedMatrix(gate_f); + } + } + } + return gates_fused; } @@ -338,7 +350,7 @@ class BasicGateFuser final : public Fuser { std::vector& gates_fused) { auto pgate = gates_lat[q][k]; - GateFused gate_f = {pgate->kind, pgate->time, {q}, pgate, {}}; + GateFused gate_f = {pgate->kind, pgate->time, {q}, pgate, {}, {}}; gate_f.gates.push_back(pgate); k = Advance(k + 1, gates_lat[q], gate_f.gates); diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index 8db3ceae..08a40d11 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -152,29 +152,29 @@ class MultiQubitGateFuser final : public Fuser { }; /** - * Stores sets of gates that can be applied together. Note that - * gates fused with this method are not multiplied together until - * ApplyFusedGate is called on the output. To respect specific time - * boundaries while fusing gates, use the other version of this method below. + * Stores sets of gates that can be applied together. To respect specific + * time boundaries while fusing gates, use the other version of this method + * below. * @param param Options for gate fusion. * @param num_qubits The number of qubits acted on by 'gates'. * @param gates The gates (or pointers to the gates) to be fused. * Gate times of the gates that act on the same qubits should be ordered. * Gates that are out of time order should not cross the time boundaries * set by measurement gates. + * @param fuse_matrix If true, multiply gate matrices together. * @return A vector of fused gate objects. Each element is a set of gates * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates(const Parameter& param, unsigned num_qubits, - const std::vector& gates) { - return FuseGates(param, num_qubits, gates.cbegin(), gates.cend(), {}); + const std::vector& gates, + bool fuse_matrix = true) { + return FuseGates( + param, num_qubits, gates.cbegin(), gates.cend(), {}, fuse_matrix); } /** - * Stores sets of gates that can be applied together. Note that - * gates fused with this method are not multiplied together until - * ApplyFusedGate is called on the output. + * Stores sets of gates that can be applied together. * @param param Options for gate fusion. * @param num_qubits The number of qubits acted on by 'gates'. * @param gates The gates (or pointers to the gates) to be fused. @@ -184,42 +184,43 @@ class MultiQubitGateFuser final : public Fuser { * @param times_to_split_at Ordered list of time steps (boundaries) at which * to separate fused gates. Each element of the output will contain gates * from a single 'window' in this list. + * @param fuse_matrix If true, multiply gate matrices together. * @return A vector of fused gate objects. Each element is a set of gates * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates( const Parameter& param, unsigned num_qubits, const std::vector& gates, - const std::vector& times_to_split_at) { + const std::vector& times_to_split_at, + bool fuse_matrix = true) { return FuseGates(param, num_qubits, gates.cbegin(), gates.cend(), - times_to_split_at); + times_to_split_at, fuse_matrix); } /** - * Stores sets of gates that can be applied together. Note that - * gates fused with this method are not multiplied together until - * ApplyFusedGate is called on the output. To respect specific time - * boundaries while fusing gates, use the other version of this method below. + * Stores sets of gates that can be applied together. To respect specific + * time boundaries while fusing gates, use the other version of this method + * below. * @param param Options for gate fusion. * @param num_qubits The number of qubits acted on by gates. * @param gfirst, glast The iterator range [gfirst, glast) to fuse gates * (or pointers to gates) in. Gate times of the gates that act on the same * qubits should be ordered. Gates that are out of time order should not * cross the time boundaries set by measurement gates. + * @param fuse_matrix If true, multiply gate matrices together. * @return A vector of fused gate objects. Each element is a set of gates * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates( const Parameter& param, unsigned num_qubits, typename std::vector::const_iterator gfirst, - typename std::vector::const_iterator glast) { - return FuseGates(param, num_qubits, gfirst, glast, {}); + typename std::vector::const_iterator glast, + bool fuse_matrix = true) { + return FuseGates(param, num_qubits, gfirst, glast, {}, fuse_matrix); } /** - * Stores sets of gates that can be applied together. Note that - * gates fused with this method are not multiplied together until - * ApplyFusedGate is called on the output. + * Stores sets of gates that can be applied together. * @param param Options for gate fusion. * @param num_qubits The number of qubits acted on by gates. * @param gfirst, glast The iterator range [gfirst, glast) to fuse gates @@ -230,6 +231,7 @@ class MultiQubitGateFuser final : public Fuser { * @param times_to_split_at Ordered list of time steps (boundaries) at which * to separate fused gates. Each element of the output will contain gates * from a single 'window' in this list. + * @param fuse_matrix If true, multiply gate matrices together. * @return A vector of fused gate objects. Each element is a set of gates * acting on a specific pair of qubits which can be applied as a group. */ @@ -237,7 +239,8 @@ class MultiQubitGateFuser final : public Fuser { const Parameter& param, unsigned num_qubits, typename std::vector::const_iterator gfirst, typename std::vector::const_iterator glast, - const std::vector& times_to_split_at) { + const std::vector& times_to_split_at, + bool fuse_matrix = true) { std::vector fused_gates; if (gfirst >= glast) return fused_gates; @@ -420,7 +423,7 @@ class MultiQubitGateFuser final : public Fuser { // Assume fgate.qubits (gate.qubits) are sorted. fused_gates.push_back({fgate.parent->kind, fgate.parent->time, std::move(fgate.qubits), fgate.parent, - std::move(fgate.gates)}); + std::move(fgate.gates), {}}); if (fgate.visited != kMeaCnt) { ++stat.num_fused_gates; @@ -433,6 +436,14 @@ class MultiQubitGateFuser final : public Fuser { } } + if (fuse_matrix) { + for (auto& fgate : fused_gates) { + if (fgate.kind != gate::kMeasurement && fgate.kind != gate::kDecomp) { + CalculateFusedMatrix(fgate); + } + } + } + PrintStat(param.verbosity, stat, fused_gates); return fused_gates; @@ -496,14 +507,14 @@ class MultiQubitGateFuser final : public Fuser { if (fgate.visited == kMeaCnt || fgate.qubits.size() >= max_fused_size || fgate.parent->unfusible) { if (fgate.visited != kMeaCnt) { - ++stat.num_fused_gates; + ++stat.num_fused_gates; } fgate.visited = kFinal; fused_gates.push_back({fgate.parent->kind, fgate.parent->time, std::move(fgate.qubits), fgate.parent, - std::move(fgate.gates)}); + std::move(fgate.gates), {}}); continue; } @@ -528,7 +539,7 @@ class MultiQubitGateFuser final : public Fuser { fused_gates.push_back({fgate->parent->kind, fgate->parent->time, std::move(fgate->qubits), fgate->parent, - std::move(fgate->gates)}); + std::move(fgate->gates), {}}); ++stat.num_fused_gates; } @@ -589,7 +600,7 @@ class MultiQubitGateFuser final : public Fuser { fused_gates.push_back({ogate1->parent->kind, ogate1->parent->time, std::move(ogate1->qubits), ogate1->parent, - std::move(ogate1->gates)}); + std::move(ogate1->gates), {}}); ++stat.num_fused_gates; } @@ -988,7 +999,7 @@ class MultiQubitGateFuser final : public Fuser { static void PrintStat(unsigned verbosity, const Stat& stat, const std::vector& fused_gates) { - if (verbosity == 0) return; + if (verbosity < 3) return; if (stat.num_controlled_gates > 0) { IO::messagef("%lu controlled gates\n", stat.num_controlled_gates); @@ -1017,7 +1028,7 @@ class MultiQubitGateFuser final : public Fuser { IO::messagef(" gates are fused into %lu gates\n", stat.num_fused_gates); - if (verbosity == 1) return; + if (verbosity < 5) return; IO::messagef("fused gate qubits:\n"); for (const auto g : fused_gates) { diff --git a/lib/gate_appl.h b/lib/gate_appl.h index 59b60082..8601e6f2 100644 --- a/lib/gate_appl.h +++ b/lib/gate_appl.h @@ -136,13 +136,12 @@ template inline void ApplyFusedGate(const Simulator& simulator, const Gate& gate, typename Simulator::State& state) { if (gate.kind != gate::kMeasurement) { - using fp_type = typename Simulator::fp_type; - auto matrix = CalculateFusedMatrix(gate); if (gate.parent->controlled_by.size() == 0) { - simulator.ApplyGate(gate.qubits, matrix.data(), state); + simulator.ApplyGate(gate.qubits, gate.matrix.data(), state); } else { simulator.ApplyControlledGate(gate.qubits, gate.parent->controlled_by, - gate.parent->cmask, matrix.data(), state); + gate.parent->cmask, gate.matrix.data(), + state); } } } @@ -160,9 +159,9 @@ template inline void ApplyFusedGateDagger(const Simulator& simulator, const Gate& gate, typename Simulator::State& state) { if (gate.kind != gate::kMeasurement) { - using fp_type = typename Simulator::fp_type; - auto matrix = CalculateFusedMatrix(gate); + auto matrix = gate.matrix; MatrixDagger(unsigned{1} << gate.qubits.size(), matrix); + if (gate.parent->controlled_by.size() == 0) { simulator.ApplyGate(gate.qubits, matrix.data(), state); } else { diff --git a/lib/hybrid.h b/lib/hybrid.h index 0ce98b63..d0189efc 100644 --- a/lib/hybrid.h +++ b/lib/hybrid.h @@ -40,6 +40,7 @@ struct HybridSimulator final { // Note that one can use "struct GateHybrid : public Gate {" in C++17. struct GateHybrid { using GateKind = HybridSimulator::GateKind; + using fp_type = HybridSimulator::fp_type; GateKind kind; unsigned time; @@ -554,7 +555,13 @@ struct HybridSimulator final { const Simulator& simulator, typename Simulator::State& state) { for (std::size_t i = i0; i < i1; ++i) { - ApplyFusedGate(simulator, gates[i], state); + if (gates[i].matrix.size() > 0) { + ApplyFusedGate(simulator, gates[i], state); + } else { + auto gate = gates[i]; + CalculateFusedMatrix(gate); + ApplyFusedGate(simulator, gate, state); + } } } diff --git a/lib/run_qsim.h b/lib/run_qsim.h index bbc401b6..b0aad9f3 100644 --- a/lib/run_qsim.h +++ b/lib/run_qsim.h @@ -79,7 +79,7 @@ struct QSimRunner final { double t0 = 0.0; double t1 = 0.0; - if (param.verbosity > 0) { + if (param.verbosity > 1) { t0 = GetTime(); } @@ -96,17 +96,33 @@ struct QSimRunner final { state_space.SetStateZero(state); Simulator simulator = factory.CreateSimulator(); + if (param.verbosity > 1) { + t1 = GetTime(); + IO::messagef("init time is %g seconds.\n", t1 - t0); + t0 = GetTime(); + } + auto fused_gates = Fuser::FuseGates(param, circuit.num_qubits, circuit.gates, times_to_measure_at); + if (fused_gates.size() == 0 && circuit.gates.size() > 0) { return false; } + if (param.verbosity > 1) { + t1 = GetTime(); + IO::messagef("fuse time is %g seconds.\n", t1 - t0); + } + + if (param.verbosity > 0) { + t0 = GetTime(); + } + unsigned cur_time_index = 0; // Apply fused gates. for (std::size_t i = 0; i < fused_gates.size(); ++i) { - if (param.verbosity > 1) { + if (param.verbosity > 3) { t1 = GetTime(); } @@ -116,7 +132,7 @@ struct QSimRunner final { return false; } - if (param.verbosity > 1) { + if (param.verbosity > 3) { double t2 = GetTime(); IO::messagef("gate %lu done in %g seconds.\n", i, t2 - t1); } @@ -132,7 +148,7 @@ struct QSimRunner final { if (param.verbosity > 0) { double t2 = GetTime(); - IO::messagef("time elapsed %g seconds.\n", t2 - t0); + IO::messagef("time is %g seconds.\n", t2 - t0); } return true; @@ -159,7 +175,7 @@ struct QSimRunner final { double t0 = 0.0; double t1 = 0.0; - if (param.verbosity > 0) { + if (param.verbosity > 1) { t0 = GetTime(); } @@ -168,16 +184,33 @@ struct QSimRunner final { StateSpace state_space = factory.CreateStateSpace(); Simulator simulator = factory.CreateSimulator(); + if (param.verbosity > 1) { + t1 = GetTime(); + IO::messagef("init time is %g seconds.\n", t1 - t0); + t0 = GetTime(); + } + auto fused_gates = Fuser::FuseGates(param, circuit.num_qubits, circuit.gates); + if (fused_gates.size() == 0 && circuit.gates.size() > 0) { return false; } + measure_results.reserve(fused_gates.size()); + if (param.verbosity > 1) { + t1 = GetTime(); + IO::messagef("fuse time is %g seconds.\n", t1 - t0); + } + + if (param.verbosity > 0) { + t0 = GetTime(); + } + // Apply fused gates. for (std::size_t i = 0; i < fused_gates.size(); ++i) { - if (param.verbosity > 1) { + if (param.verbosity > 3) { t1 = GetTime(); } @@ -187,7 +220,7 @@ struct QSimRunner final { return false; } - if (param.verbosity > 1) { + if (param.verbosity > 3) { double t2 = GetTime(); IO::messagef("gate %lu done in %g seconds.\n", i, t2 - t1); } @@ -195,7 +228,7 @@ struct QSimRunner final { if (param.verbosity > 0) { double t2 = GetTime(); - IO::messagef("time elapsed %g seconds.\n", t2 - t0); + IO::messagef("simu time is %g seconds.\n", t2 - t0); } return true; diff --git a/tests/fuser_mqubit_test.cc b/tests/fuser_mqubit_test.cc index 761b1654..c41c6549 100644 --- a/tests/fuser_mqubit_test.cc +++ b/tests/fuser_mqubit_test.cc @@ -44,6 +44,7 @@ enum DummyGateKind { struct DummyGate { using GateKind = DummyGateKind; + using fp_type = float; GateKind kind; unsigned time; @@ -263,7 +264,7 @@ TEST(FuserMultiQubitTest, RandomCircuit1) { for (unsigned q = 2; q <= 6; ++q) { param.max_fused_size = q; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); } @@ -272,7 +273,7 @@ TEST(FuserMultiQubitTest, RandomCircuit1) { param.max_fused_size = q; auto fused_gates = Fuser::FuseGates( param, num_qubits, circuit.begin(), circuit.end(), - {5000, 7000, 25000, 37000}); + {5000, 7000, 25000, 37000}, false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); } @@ -301,7 +302,7 @@ TEST(FuserMultiQubitTest, RandomCircuit2) { for (unsigned q = 2; q <= 6; ++q) { param.max_fused_size = q; auto fused_gates = Fuser::FuseGates( - param, num_qubits, pcircuit.begin(), pcircuit.end()); + param, num_qubits, pcircuit.begin(), pcircuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); } @@ -309,7 +310,8 @@ TEST(FuserMultiQubitTest, RandomCircuit2) { for (unsigned q = 2; q <= 6; ++q) { param.max_fused_size = q; auto fused_gates = Fuser::FuseGates( - param, num_qubits, pcircuit.begin(), pcircuit.end(), {300, 700, 2400}); + param, num_qubits, pcircuit.begin(), pcircuit.end(), + {300, 700, 2400}, false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); } @@ -404,7 +406,7 @@ TEST(FuserMultiQubitTest, SmallCircuits) { param.max_fused_size = 4; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); EXPECT_EQ(fused_gates.size(), 1); @@ -424,7 +426,7 @@ TEST(FuserMultiQubitTest, SmallCircuits) { param.max_fused_size = 4; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); EXPECT_EQ(fused_gates.size(), 3); @@ -447,7 +449,7 @@ TEST(FuserMultiQubitTest, SmallCircuits) { param.max_fused_size = 6; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); EXPECT_EQ(fused_gates.size(), 1); @@ -472,7 +474,7 @@ TEST(FuserMultiQubitTest, SmallCircuits) { param.max_fused_size = 6; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); EXPECT_EQ(fused_gates.size(), 3); @@ -488,7 +490,7 @@ TEST(FuserMultiQubitTest, SmallCircuits) { param.max_fused_size = 3; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); EXPECT_EQ(fused_gates.size(), 2); @@ -505,7 +507,7 @@ TEST(FuserMultiQubitTest, SmallCircuits) { param.max_fused_size = 3; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); EXPECT_EQ(fused_gates.size(), 3); @@ -523,7 +525,7 @@ TEST(FuserMultiQubitTest, SmallCircuits) { param.max_fused_size = 3; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); EXPECT_EQ(fused_gates.size(), 3); @@ -542,7 +544,7 @@ TEST(FuserMultiQubitTest, SmallCircuits) { param.max_fused_size = 3; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); EXPECT_EQ(fused_gates.size(), 4); @@ -563,7 +565,7 @@ TEST(FuserMultiQubitTest, SmallCircuits) { param.max_fused_size = 5; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); EXPECT_EQ(fused_gates.size(), 3); @@ -579,7 +581,7 @@ TEST(FuserMultiQubitTest, SmallCircuits) { param.max_fused_size = 3; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); EXPECT_EQ(fused_gates.size(), 1); @@ -595,7 +597,7 @@ TEST(FuserMultiQubitTest, SmallCircuits) { param.max_fused_size = 3; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); EXPECT_EQ(fused_gates.size(), 1); @@ -613,7 +615,7 @@ TEST(FuserMultiQubitTest, SmallCircuits) { param.max_fused_size = 3; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); EXPECT_EQ(fused_gates.size(), 4); @@ -633,7 +635,7 @@ TEST(FuserMultiQubitTest, SmallCircuits) { { param.max_fused_size = 3; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); EXPECT_EQ(fused_gates.size(), 4); @@ -642,7 +644,7 @@ TEST(FuserMultiQubitTest, SmallCircuits) { { param.max_fused_size = 5; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); @@ -680,7 +682,7 @@ TEST(FuserMultiQubitTest, ValidTimeOrder) { param.max_fused_size = 2; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 14); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); @@ -705,7 +707,7 @@ TEST(FuserMultiQubitTest, ValidTimeOrder) { param.max_fused_size = 6; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 3); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); @@ -724,7 +726,7 @@ TEST(FuserMultiQubitTest, ValidTimeOrder) { param.max_fused_size = 3; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 4); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); @@ -745,7 +747,7 @@ TEST(FuserMultiQubitTest, ValidTimeOrder) { param.max_fused_size = 5; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 3); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); @@ -763,7 +765,7 @@ TEST(FuserMultiQubitTest, ValidTimeOrder) { param.max_fused_size = 3; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 4); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); @@ -783,7 +785,7 @@ TEST(FuserMultiQubitTest, ValidTimeOrder) { { param.max_fused_size = 3; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); } @@ -791,7 +793,7 @@ TEST(FuserMultiQubitTest, ValidTimeOrder) { { param.max_fused_size = 5; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); } @@ -814,8 +816,8 @@ TEST(FuserMultiQubitTest, ValidTimeOrder) { param.max_fused_size = 4; std::vector time_boundary = {3}; - auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end(), time_boundary); + auto fused_gates = Fuser::FuseGates(param, num_qubits, circuit.begin(), + circuit.end(), time_boundary, false); EXPECT_EQ(fused_gates.size(), 2); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); @@ -838,7 +840,7 @@ TEST(FuserMultiQubitTest, ValidTimeOrder) { param.max_fused_size = 4; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 3); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); @@ -860,7 +862,7 @@ TEST(FuserMultiQubitTest, InvalidTimeOrder) { param.max_fused_size = 3; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 0); } @@ -876,7 +878,7 @@ TEST(FuserMultiQubitTest, InvalidTimeOrder) { param.max_fused_size = 2; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 0); } @@ -892,8 +894,8 @@ TEST(FuserMultiQubitTest, InvalidTimeOrder) { param.max_fused_size = 2; std::vector time_boundary = {1}; - auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end(), time_boundary); + auto fused_gates = Fuser::FuseGates(param, num_qubits, circuit.begin(), + circuit.end(), time_boundary, false); EXPECT_EQ(fused_gates.size(), 0); } @@ -909,8 +911,8 @@ TEST(FuserMultiQubitTest, InvalidTimeOrder) { param.max_fused_size = 2; std::vector time_boundary = {2}; - auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end(), time_boundary); + auto fused_gates = Fuser::FuseGates(param, num_qubits, circuit.begin(), + circuit.end(), time_boundary, false); EXPECT_EQ(fused_gates.size(), 0); } @@ -926,7 +928,7 @@ TEST(FuserMultiQubitTest, InvalidTimeOrder) { param.max_fused_size = 2; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 0); } @@ -942,7 +944,7 @@ TEST(FuserMultiQubitTest, InvalidTimeOrder) { param.max_fused_size = 2; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 0); } @@ -958,7 +960,7 @@ TEST(FuserMultiQubitTest, InvalidTimeOrder) { param.max_fused_size = 2; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 0); } @@ -974,7 +976,7 @@ TEST(FuserMultiQubitTest, InvalidTimeOrder) { param.max_fused_size = 2; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 0); } @@ -999,7 +1001,7 @@ TEST(FuserMultiQubitTest, OrphanedGates) { for (unsigned f = 2; f <= num_qubits; ++f) { param.max_fused_size = f; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_TRUE(TestFusedGates(num_qubits, circuit, fused_gates)); EXPECT_EQ(fused_gates.size(), (num_qubits - 1) / f + 1); @@ -1018,7 +1020,7 @@ TEST(FuserMultiQubitTest, OrphanedGates) { param.max_fused_size = 2; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 2); } @@ -1035,7 +1037,7 @@ TEST(FuserMultiQubitTest, OrphanedGates) { param.max_fused_size = 2; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 2); } @@ -1049,7 +1051,7 @@ TEST(FuserMultiQubitTest, OrphanedGates) { param.max_fused_size = 2; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 2); } @@ -1063,7 +1065,7 @@ TEST(FuserMultiQubitTest, OrphanedGates) { param.max_fused_size = 2; auto fused_gates = Fuser::FuseGates( - param, num_qubits, circuit.begin(), circuit.end()); + param, num_qubits, circuit.begin(), circuit.end(), false); EXPECT_EQ(fused_gates.size(), 2); } diff --git a/tests/mps_simulator_test.cc b/tests/mps_simulator_test.cc index 605a29c0..c607d1aa 100644 --- a/tests/mps_simulator_test.cc +++ b/tests/mps_simulator_test.cc @@ -823,7 +823,8 @@ TEST(MPSSimulator, ApplyFusedGateLeft) { auto gate3 = GateHd::Create(0, 1); GateFused> fgate1{kGateCZ, 2, {0, 1}, &gate1, - {&gate2, &gate3}}; + {&gate2, &gate3}, {}}; + CalculateFusedMatrix(fgate1); auto mps = ss.Create(3, 4); ss.SetStateZero(mps); ApplyFusedGate(sim, fgate1, mps); @@ -857,7 +858,8 @@ TEST(MPSSimulator, ApplyFusedGateRight) { auto gate3 = GateHd::Create(0, 2); GateFused> fgate1{kGateCZ, 2, {1, 2}, &gate1, - {&gate2, &gate3}}; + {&gate2, &gate3}, {}}; + CalculateFusedMatrix(fgate1); auto mps = ss.Create(3, 4); ss.SetStateZero(mps); ApplyFusedGate(sim, fgate1, mps); @@ -891,7 +893,8 @@ TEST(MPSSimulator, ApplyFusedGateMiddle) { auto gate3 = GateHd::Create(0, 2); GateFused> fgate1{kGateCZ, 2, {1, 2}, &gate1, - {&gate2, &gate3}}; + {&gate2, &gate3}, {}}; + CalculateFusedMatrix(fgate1); auto mps = ss.Create(4, 4); ss.SetStateZero(mps); ApplyFusedGate(sim, fgate1, mps); diff --git a/tests/simulator_testfixture.h b/tests/simulator_testfixture.h index 717ff47d..05cad5a6 100644 --- a/tests/simulator_testfixture.h +++ b/tests/simulator_testfixture.h @@ -224,7 +224,8 @@ void TestApplyGate5(const Factory& factory) { auto gate25 = GateRX::Create(11, 4, 0.3); GateFused> fgate1{kGateCZ, 2, {0, 1}, &gate11, - {&gate1, &gate2, &gate6, &gate7, &gate11, &gate12, &gate13}}; + {&gate1, &gate2, &gate6, &gate7, &gate11, &gate12, &gate13}, {}}; + CalculateFusedMatrix(fgate1); ApplyFusedGate(simulator, fgate1, state); EXPECT_NEAR(state_space.Norm(state), 1, 1e-6); @@ -245,7 +246,8 @@ void TestApplyGate5(const Factory& factory) { } GateFused> fgate2{kGateIS, 4, {1, 2}, &gate14, - {&gate3, &gate8, &gate14, &gate15, &gate16}}; + {&gate3, &gate8, &gate14, &gate15, &gate16}, {}}; + CalculateFusedMatrix(fgate2); ApplyFusedGate(simulator, fgate2, state); EXPECT_NEAR(state_space.Norm(state), 1, 1e-6); @@ -266,7 +268,8 @@ void TestApplyGate5(const Factory& factory) { } GateFused> fgate3{kGateCNot, 6, {2, 3}, &gate17, - {&gate4, &gate9, &gate17, &gate18, &gate19}}; + {&gate4, &gate9, &gate17, &gate18, &gate19},{}}; + CalculateFusedMatrix(fgate3); ApplyFusedGate(simulator, fgate3, state); EXPECT_NEAR(state_space.Norm(state), 1, 1e-6); @@ -287,7 +290,8 @@ void TestApplyGate5(const Factory& factory) { } GateFused> fgate4{kGateFS, 8, {3, 4}, &gate20, - {&gate5, &gate10, &gate20, &gate21, &gate22}}; + {&gate5, &gate10, &gate20, &gate21, &gate22}, {}}; + CalculateFusedMatrix(fgate4); ApplyFusedGate(simulator, fgate4, state); EXPECT_NEAR(state_space.Norm(state), 1, 1e-6); @@ -307,7 +311,9 @@ void TestApplyGate5(const Factory& factory) { EXPECT_NEAR(std::imag(ampl3), -0.00987822, 1e-6); } - GateFused> fgate5{kGateCP, 10, {0, 1}, &gate23, {&gate23}}; + GateFused> fgate5{kGateCP, 10, {0, 1}, &gate23, + {&gate23}, {}}; + CalculateFusedMatrix(fgate5); ApplyFusedGate(simulator, fgate5, state); EXPECT_NEAR(state_space.Norm(state), 1, 1e-6); diff --git a/tests/statespace_testfixture.h b/tests/statespace_testfixture.h index 2a41c047..a6df9a29 100644 --- a/tests/statespace_testfixture.h +++ b/tests/statespace_testfixture.h @@ -463,17 +463,18 @@ void TestNormAndInnerProductSmall(const Factory& factory) { template void TestNormAndInnerProduct(const Factory& factory) { + using Simulator = typename Factory::Simulator; + using StateSpace = typename Simulator::StateSpace; + using State = typename StateSpace::State; + using fp_type = typename StateSpace::fp_type; + using Runner = QSimRunner>, Factory>; + unsigned depth = 8; std::stringstream ss(circuit_string); - Circuit> circuit; + Circuit> circuit; EXPECT_TRUE(CircuitQsimParser::FromStream(depth, provider, ss, circuit)); - circuit.gates.emplace_back(GateT::Create(depth + 1, 0)); - - using Simulator = typename Factory::Simulator; - using StateSpace = typename Simulator::StateSpace; - using State = typename StateSpace::State; - using Runner = QSimRunner>, Factory>; + circuit.gates.emplace_back(GateT::Create(depth + 1, 0)); StateSpace state_space = factory.CreateStateSpace(); State state0 = state_space.Create(circuit.num_qubits); @@ -773,17 +774,18 @@ void TestMeasurementSmall(const Factory& factory, bool cuda = false) { template void TestMeasurementLarge(const Factory& factory) { + using Simulator = typename Factory::Simulator; + using StateSpace = typename Simulator::StateSpace; + using State = typename StateSpace::State; + using fp_type = typename StateSpace::fp_type; + using Runner = QSimRunner>, Factory>; + unsigned depth = 20; std::stringstream ss(circuit_string); - Circuit> circuit; + Circuit> circuit; EXPECT_TRUE(CircuitQsimParser::FromStream(depth, provider, ss, circuit)); - using Simulator = typename Factory::Simulator; - using StateSpace = typename Simulator::StateSpace; - using State = typename StateSpace::State; - using Runner = QSimRunner>, Factory>; - StateSpace state_space = factory.CreateStateSpace(); State state = state_space.Create(circuit.num_qubits); diff --git a/tests/unitary_calculator_testfixture.h b/tests/unitary_calculator_testfixture.h index 3b7707c3..cd77ca43 100644 --- a/tests/unitary_calculator_testfixture.h +++ b/tests/unitary_calculator_testfixture.h @@ -425,8 +425,10 @@ void TestApplyFusedGate() { std::vector gates = {Cirq::H::Create(0, 0), Cirq::H::Create(1, 0)}; - GateFused fgate{Cirq::kH, 0, {0}, &gates[0], {&gates[0], &gates[1]}}; + GateFused fgate {Cirq::kH, 0, {0}, &gates[0], + {&gates[0], &gates[1]}, {}}; + CalculateFusedMatrix(fgate); ApplyFusedGate(uc, fgate, u); unsigned size = 1 << num_qubits; From a058fc99c04cf412ac0084a8bd1d5c20895f24e3 Mon Sep 17 00:00:00 2001 From: Matthew Neeley Date: Mon, 25 Oct 2021 08:49:58 -0700 Subject: [PATCH 133/246] Remove print in qsim_simulator when sampling from final state vector (#461) This print results in very verbose output when using QSimSimulator. Could also convert it to debug logging if people think it's still helpful to have. Review: @95-martin-orion --- qsimcirq/qsim_simulator.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 34bbde2e..806c910c 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -332,10 +332,6 @@ def _sample_measure_results( sampler_fn = self._sim_module.qsim_sample if not noisy and program.are_all_measurements_terminal() and repetitions > 1: - print( - "Provided circuit has no intermediate measurements. " - + "Sampling repeatedly from final state vector." - ) # Measurements must be replaced with identity gates to sample properly. # Simply removing them may omit qubits from the circuit. for i in range(len(program.moments)): From f76aa22221dd5c2812fe5acb8f91daf23207b4e2 Mon Sep 17 00:00:00 2001 From: Ricardo Olenewa Date: Mon, 25 Oct 2021 16:16:41 +0000 Subject: [PATCH 134/246] Fixes href rendering in docs/tutorials/multinode.md (Step 1 -> Clone this repo) --- docs/tutorials/multinode.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/tutorials/multinode.md b/docs/tutorials/multinode.md index 2cf434c7..746ceda8 100644 --- a/docs/tutorials/multinode.md +++ b/docs/tutorials/multinode.md @@ -22,9 +22,11 @@ tutorial, open the [Cloud Shell in the Cloud Console](https://console.cloud.goog ### Clone this repo In your Cloud Shell window, clone this Github repo. + ``` bash git clone https://github.com/quantumlib/qsim.git ``` + If you get an error saying something like `qsim already exists`, you may need to delete the `qsim` directory with `rm -rf qsim` and rerun the clone command. From 250967901574d17dadb15dccda02b52fc8e937f6 Mon Sep 17 00:00:00 2001 From: Ricardo Olenewa Date: Mon, 25 Oct 2021 16:47:55 +0000 Subject: [PATCH 135/246] Fixes href rendering in docs/tutorials/multinode.md --- docs/tutorials/multinode.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/tutorials/multinode.md b/docs/tutorials/multinode.md index 746ceda8..8d425cc2 100644 --- a/docs/tutorials/multinode.md +++ b/docs/tutorials/multinode.md @@ -33,15 +33,18 @@ to delete the `qsim` directory with `rm -rf qsim` and rerun the clone command. ### Change directory Change directory to the tutorial: + ``` bash cd qsim/docs/tutorials/multinode/terraform ``` + This is where you will use `terraform` to create the HTCondor cluster required to run your jobs. ### Edit `init.sh` file to match your environment Using your favorite text file editor, open the `init.sh` file. The first few lines should look like this: + ```bash # ---- Edit below -----# @@ -49,6 +52,7 @@ export TF_VAR_project=[USER_PROJECT] export TF_VAR_zone=us-east4-c export TF_VAR_region=us-east4 ``` + Replace `[USER_PROJECT]` with the project name you chose on the `Before you begin` page. @@ -63,9 +67,11 @@ your project will create new jobs. ### Source the `init.sh` file The edited `init.sh` file should be "sourced" in the cloud shell: + ``` bash source init.sh ``` + Respond `Agree` to any pop-ups that request permissions on the Google Cloud platform. The final outcome of this script will include: @@ -94,10 +100,13 @@ Terraform has been successfully initialized! For convenience, some terraform commands are prepared in a `Makefile`. This means you can now create your cluster, with a simple `make` command. + ```bash make apply ``` + A successful run will show: + ``` Apply complete! Resources: 4 added, 0 changed, 0 destroyed. ``` @@ -111,10 +120,13 @@ commands to submit and monitor jobs on HTCondor. ### List VMs that were created by HTCondor To see the VMs created by HTCondor, run: + ```bash gcloud compute instances list ``` + At this point in the tutorial, you will see two instances listed: + ``` NAME: c-manager ZONE: us-central1-a @@ -145,14 +157,17 @@ complete. This new window is logged into your HTCondor cluster. You will see a command prompt that looks something like this: + ```bash [mylogin@c-submit ~]$ ``` + The following steps should be performed in this window. ### Checking the status You can run `condor_q` to verify if the HTCondor install is completed. The output should look something like this: + ``` -- Schedd: c-submit.c.quantum-htcondor-14.internal : <10.150.0.2:9618?... @ 08/18/21 18:37:50 OWNER BATCH_NAME SUBMITTED DONE RUN IDLE HOLD TOTAL JOB_IDS @@ -161,6 +176,7 @@ Total for query: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 su Total for drj: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended Total for all users: 0 jobs; 0 completed, 0 removed, 0 idle, 0 running, 0 held, 0 suspended ``` + If you get `command not found`, you will need to wait a few minutes for the HTCondor install to complete. ## 4. Get the sample code and run it @@ -172,10 +188,13 @@ sample jobs have been provided in the Github repo. On the submit node, you can clone the repo to get access to previously created submission files: + ```bash git clone https://github.com/quantumlib/qsim.git ``` + Then cd to the tutorial directory. + ```bash cd qsim/docs/tutorials/multinode ``` From dfd8f6441164578711c7870cad02eb9a12168fa6 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Tue, 26 Oct 2021 18:17:58 +0200 Subject: [PATCH 136/246] Verbosity levels. --- docs/usage.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/usage.md b/docs/usage.md index 14dbcda8..33b7f317 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -28,6 +28,17 @@ Sample circuits are provided in qsim_base computes all the amplitudes and just prints the first eight of them (or a smaller number for 1- or 2-qubit circuits). +Verbosity levels are described in the following table. + +| Verbosity level | Description | +|-----------------|-------------| +| 0 | no additional information| +| 1 | add total simulation runtime| +| 2 | add initialization runtime and fuser runtime| +| 3 | add basic fuser statistics| +| 4 | add simulation runtime for each fused gate| +| 5 | additional fuser information (qubit indices for each fused gate)| + Example: ``` ./qsim_base.x -c ../circuits/circuit_q24 -d 16 -t 8 -v 1 From c9bc8844ceb87c22eb5b812f53b4df4617532bed Mon Sep 17 00:00:00 2001 From: Ricardo Olenewa Date: Tue, 26 Oct 2021 16:59:56 +0000 Subject: [PATCH 137/246] Fine polish on the setup tutorials. --- docs/tutorials/gcp_cpu.md | 86 ++++++++++++++++++++------------------- docs/tutorials/gcp_gpu.md | 53 ++++++++++++------------ 2 files changed, 72 insertions(+), 67 deletions(-) diff --git a/docs/tutorials/gcp_cpu.md b/docs/tutorials/gcp_cpu.md index f68e61e5..0795e622 100644 --- a/docs/tutorials/gcp_cpu.md +++ b/docs/tutorials/gcp_cpu.md @@ -11,19 +11,20 @@ Follow the instructions in the guide to create a VM. In addition to the guidance under the Create a Linux VM instance heading, ensure that your VM has the following properties: -* In the Machine Configuration section, in the Machine family options, - click on the **Compute Optimized** filter. -* In the Machine Configuration section, in the Machine family options, in - the Series option, choose **C2**. -* In the Machine Configuration section, in the Machine family options, in - the Machine type option, choose **c2-standard-16**. This option gives - you 16 virtual CPUS and 64MB of RAM. - * This choice is for demonstration purposes only. For a live experiment, - see [Choosing hardware for your qsim simulation](/qsim/choose_hw) -* In the Boot disk section, click the Change button, and choose - Container-Optimized OS. This overrides step 3 in the quickstart guide. -* In the Firewall section, ensure that both the **Allow HTTP traffic** - checkbox and **Allow HTTPS traffic** checkbox have marks. +* In the **Machine Configuration** section: + 1. Select the tab for the **Compute Optimized** machine family. + 2. In the machine **Series** option, choose **C2**. + 3. In the **Machine type** option, choose **c2-standard-16**. This option + gives you 16 virtual CPUS and 64MB of RAM. + Note: This choice is for demonstration purposes only. For a live + experiment, see [Choosing hardware for your qsim + simulation](/qsim/choose_hw). +* In the **Boot disk section**, click the **Change** button, and choose + **Container-Optimized** operating system. This overrides the seletion in + step 3 in [Create a Linux VM + instance](https://cloud.google.com/compute/docs/quickstart-linux#create_a_linux_vm_instance). +* In the **Firewall** section, ensure that both the **Allow HTTP traffic** + checkbox and the **Allow HTTPS traffic** checkbox are selected. When Google Cloud finishes creating the VM, you can see your VM listed in the [Compute Instances dashboard](https://pantheon.corp.google.com/compute/instances) @@ -62,30 +63,29 @@ machine to your virtual machine. ## 3. Start the qsim Docker container on your virtual machine -On your VM, start the qsim container by typing the following command at the -command prompt, on your VM from the previous step. +1. On the VM that you just created, start the qsim container: -``` -docker run -v `pwd`:/homedir -p 8888:8888 gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest & -``` + ``` + docker run -v `pwd`:/homedir -p 8888:8888 gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest & + ``` -If you see a `permission denied` error message, you might need to add `sudo` -before your Docker command. For more information about running Docker, see the -[`docker run` command reference](https://docs.docker.com/engine/reference/run/#general-form). + If you see a `permission denied` error message, you might need to add `sudo` + before your Docker command. For more information about running Docker, see the + [`docker run` command reference](https://docs.docker.com/engine/reference/run/#general-form). -As Docker downloads and starts the container, it prints many lines of output. -The last few lines should be similar to the following output: +2. Verify the output from Docker when as it downloads and starts the container. + The last few lines should be similar to the following output: -``` -To access the notebook, open this file in a browser: - file:///root/.local/share/jupyter/runtime/nbserver-1-open.html -Or copy and paste one of these URLs: - http://e1f7a7cca9fa:8888/?token=aa16e1b6d3f51c58928037d34cc6854dac47347dd4c0eae5 - or http://127.0.0.1:8888/?token=aa16e1b6d3f51c58928037d34cc6854dac47347dd4c0eae5 -``` + ``` + To access the notebook, open this file in a browser: + file:///root/.local/share/jupyter/runtime/nbserver-1-open.html + Or copy and paste one of these URLs: + http://e1f7a7cca9fa:8888/?token=aa16e1b6d3f51c58928037d34cc6854dac47347dd4c0eae5 + or http://127.0.0.1:8888/?token=aa16e1b6d3f51c58928037d34cc6854dac47347dd4c0eae5 + ``` -Copy the URL in the last line of output from your console, and save it for the -next step. +3. Copy the URL in the last line of output from your console, and save it for + the next task. ## 4. Connect to your virtual machine @@ -106,18 +106,19 @@ and run your code. 1. Open the [Get Started with qsimcirq notebook](https://quantumai.google/qsim/tutorials/qsimcirq). - 2. Near the top-right corner of your notebook, click the small triangle beside + 2. Click the **Connect** drop-down menu. the Connect button to open the menu. 3. Choose the **Connect to a local runtime** option to open the Local connection settings window. Google Colab Connect to Local Runtime button - 1. In the Backend URL text field, paste the URL that you saved in step 5. - 2. Change the part of your URL that read `127.0.0.1` to `localhost`. + 4. In the **Backend URL** text field, paste the URL that you saved in + [task 3](#3_start_the_qsim_docker_container_on_your_virtual_machine). + 5. Change the part of your URL that read `127.0.0.1` to `localhost`. Google Colab Local Runtime connection window - 1. Click the **Connect** button in the Local connection settings window. + 6. Click the **Connect** button in the Local connection settings window. When your connection is ready, Colab displays a green checkmark beside the - Connected (Local) button near the top-right corner of your notebook. + Connected (Local) drop-down menu. Google Colab Local Runtime connection windo @@ -130,7 +131,8 @@ and run your code. Jupyter runs in the qsim Docker container. 1. Open a browser window. - 2. In the navigation bar, paste the URL that you copied in step 4. + 2. In the navigation bar, paste the URL that you copied in [task + 3](#3_start_the_qsim_docker_container_on_your_virtual_machine). 3. In the browser you should now see the Jupyter UI, running on your VM. The code that you execute here executes on your VM. You can navigate to qsim > @@ -143,9 +145,9 @@ and run your code. **Before you begin** - For this scenario, you can connect to your machine directly over SSH rather than - create a tunnel. In step 2.3 above, remove the second half of the command. - Instead of the following command: + For this scenario, you can connect to your machine directly over SSH + rather than create a tunnel. In [task 2, step 3](#2_prepare_your_computer) + above, remove the second half of the command. Instead of this command: ``` gcloud compute ssh [YOUR_INSTANCE_NAME] -- -L 8888:localhost:8888 @@ -157,7 +159,7 @@ and run your code. gcloud compute ssh [YOUR_INSTANCE_NAME] ``` - Either command works for the purpose of this tutorial. Continue to step 4 then + Either command works for the purpose of this tutorial. Continue to task 4 then complete the steps below, regardless of which command you use. **1. Copy the container ID for your qsim Docker container** diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index 61e686fe..119fa2c2 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -3,7 +3,7 @@ In this tutorial, you configure and test a virtual machine (VM) to run GPU-based quantum simulations on Google Cloud. -The later steps in this tutorial require you to enter several commands at the +Note: The later steps in this tutorial require you to enter several commands at the command line. Some commands might require you to add `sudo` before the command. For example, if a step asks you to type `icecream -fancy`, you might need to type `sudo icecream -fancy`. @@ -12,21 +12,22 @@ type `sudo icecream -fancy`. Follow the instructions in the [Quickstart using a Linux VM](https://cloud.google.com/compute/docs/quickstart-linux) -guide to create a VM. In addition to the guidance under the Create a Linux VM -instance heading, ensure that your VM has the following properties: - -* In the Machine Configuration section, in the Machine Family options, click - on the **GPU** filter. -* In the Machine Configuration section, in the Machine family options, in the - GPU type option, choose **NVIDIA Tesla A100**. - * In the Number of GPUs option, choose **1**. -* In the Boot disk section, click the **Change** button. - * In the Operating System option, choose **Ubuntu**. - * In the Version option, choose **20.04 LTS**. - * In the Size field, enter **30** (minimum). - * This overrides step 3 through 5 in the Quickstart guide. -* In the Firewall section, ensure that both the **Allow HTTP traffic** - checkbox and **Allow HTTPS traffic** checkbox have marks. +guide to create a VM. In addition to the guidance specified in the Create a Linux VM +instance section, ensure that your VM has the following properties: + +* In the **Machine Configuration** section: + 1. Select the tab for the **GPU** machine family. + 2. In the **GPU type** option, choose **NVIDIA Tesla A100**. + 3. In the **Number of GPUs** option, choose **1**. +* In the **Boot disk** section, click the **Change** button: + 1. In the **Operating System** option, choose **Ubuntu**. + 2. In the **Version** option, choose **20.04 LTS**. + 3. In the **Size** field, enter **30** (minimum). + 4. This overrides step 3 through 5 in the [Create a Linux + VM instance](https://cloud.google.com/compute/docs/quickstart-linux) + Quickstart. +* In the **Firewall** section, ensure that both the **Allow HTTP traffic** + checkbox and the **Allow HTTPS traffic** checkboxs are selected. When Google Cloud finishes creating the VM, you can see your VM listed in the [Compute Instances dashboard](https://pantheon.corp.google.com/compute/instances) @@ -63,11 +64,13 @@ machine to your virtual machine. ## 3. Enable your virutal machine to use the GPU -1. Install the GPU driver. In the Google Cloud documentation, in the Installing - GPU drivers guide, follow the steps provided in the following sections: +1. Install the GPU driver. Complete the steps provided in the following + sections of the [Installing GPU + drivers](https://cloud.google.com/compute/docs/gpus/install-drivers-gpu): + guide: * [Examples](https://cloud.google.com/compute/docs/gpus/install-drivers-gpu#examples), - under the Ubuntu tab. Only follow the steps for Ubuntu 20.04 (steps 3a - through 3f). + under the **Ubuntu** tab. For step 3, only perform the steps for + **Ubuntu 20.04** (steps 3a through 3f). * [Verifying the GPU driver install](https://cloud.google.com/compute/docs/gpus/install-drivers-gpu#verify-driver-install) 2. Install the CUDA toolkit. @@ -123,7 +126,7 @@ sudo apt install cmake && sudo apt install pip && pip install pybind11 3. Run `make` to compile qsim. When make detects the CUDA toolkit during compilation, make builds the GPU version of qsim automatically. 4. Run `pip install .` to install your local version of qsimcirq. -5. Verify your installation. +5. Verify your qsim installation. ```shell python3 -c "import qsimcirq; print(qsimcirq.qsim_gpu)" @@ -132,15 +135,15 @@ sudo apt install cmake && sudo apt install pip && pip install pybind11 If the installation completed successfully, the output from the command should resemble the following: - ```shell + ```none ``` ## 6. Verify your installation -You can use the code below to verify that qsim uses your GPU. You can paste the -code directly into the REPL, or paste the code in a file. +You can use the following code to verify that qsim uses your GPU. You can paste +the code directly into the REPL, or paste the code in a file. ``` # Import Cirq and qsim @@ -168,7 +171,7 @@ print(qsim_results) After a moment, you should see a result that looks similar to the following. -``` +```none [(0.7071067690849304+0j), 0j] ``` From 2e6e05e02ad56e4fbd4672acc00d398c65ba7e37 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 27 Oct 2021 08:33:32 -0700 Subject: [PATCH 138/246] Align with Cirq 0.13.1 --- qsimcirq/qsim_simulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 806c910c..39796b92 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -305,7 +305,7 @@ def _sample_measure_results( current_index = 0 for op in measurement_ops: gate = op.gate - key = protocols.measurement_key(gate) + key = protocols.measurement_key_name(gate) meas_ops[key] = op if key in bounds: raise ValueError(f"Duplicate MeasurementGate with key {key}") From d7cd507ba1b3809e568bce3625f9a51e5cc051a7 Mon Sep 17 00:00:00 2001 From: Ricardo Olenewa Date: Thu, 28 Oct 2021 16:24:37 +0000 Subject: [PATCH 139/246] Addresses comments. --- docs/tutorials/gcp_gpu.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index f5933f86..6d442051 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -23,9 +23,9 @@ instance section, ensure that your VM has the following properties: 1. In the **Operating System** option, choose **Ubuntu**. 2. In the **Version** option, choose **20.04 LTS**. 3. In the **Size** field, enter **30** (minimum). - 4. This overrides step 3 through 5 in the [Create a Linux - VM instance](https://cloud.google.com/compute/docs/quickstart-linux) - Quickstart. +* The instructions above override steps 3 through 5 in the [Create a Linux VM + instance](https://cloud.google.com/compute/docs/quickstart-linux) + Quickstart. * In the **Firewall** section, ensure that both the **Allow HTTP traffic** checkbox and the **Allow HTTPS traffic** checkboxs are selected. From 25c9a4640ef5ea4032b5d3f2a2913196f387c809 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 1 Nov 2021 12:17:50 -0700 Subject: [PATCH 140/246] Unpin qsimcirq version in noisy_qsimcirq doc --- docs/tutorials/noisy_qsimcirq.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/noisy_qsimcirq.ipynb b/docs/tutorials/noisy_qsimcirq.ipynb index 6f366593..af2e5c89 100644 --- a/docs/tutorials/noisy_qsimcirq.ipynb +++ b/docs/tutorials/noisy_qsimcirq.ipynb @@ -98,7 +98,7 @@ "try:\n", " import qsimcirq\n", "except ImportError:\n", - " !pip install qsimcirq==0.9.5 --quiet\n", + " !pip install qsimcirq --quiet\n", " import qsimcirq" ] }, @@ -284,4 +284,4 @@ "metadata": {} } ] -} \ No newline at end of file +} From f0491cccf78e5d2f5a6054f326e8da50ea74fd1d Mon Sep 17 00:00:00 2001 From: Bernardo Meurer Date: Sat, 30 Oct 2021 00:52:02 -0700 Subject: [PATCH 141/246] refactor: split dev requirements out of main requirements.txt --- .github/workflows/python_format.yml | 6 ++---- .github/workflows/testing_wheels.yml | 1 + MANIFEST.in | 1 + dev-requirements.txt | 3 +++ docs/install_qsimcirq.md | 6 ++++++ requirements.txt | 12 ++---------- setup.py | 4 ++++ 7 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 dev-requirements.txt diff --git a/.github/workflows/python_format.yml b/.github/workflows/python_format.yml index adb5f56f..213abf2e 100644 --- a/.github/workflows/python_format.yml +++ b/.github/workflows/python_format.yml @@ -24,9 +24,7 @@ jobs: with: python-version: '3.7' architecture: 'x64' - - name: Install flynt - run: cat requirements.txt | grep flynt | xargs pip install - - name: Install black - run: cat requirements.txt | grep black | xargs pip install + - name: Install dev requirements + run: pip install -r dev-requirements.txt - name: Format run: check/format-incremental diff --git a/.github/workflows/testing_wheels.yml b/.github/workflows/testing_wheels.yml index eeb4d741..55dd237a 100644 --- a/.github/workflows/testing_wheels.yml +++ b/.github/workflows/testing_wheels.yml @@ -45,6 +45,7 @@ jobs: CIBW_REPAIR_WHEEL_COMMAND_MACOS: "" # due to package and module name conflict have to temporarily move it away to run tests CIBW_BEFORE_TEST: "mv {package}/qsimcirq /tmp" + CIBW_TEST_EXTRAS: "dev" CIBW_TEST_COMMAND: "pytest {package}/qsimcirq_tests/qsimcirq_test.py && mv /tmp/qsimcirq {package}" steps: - uses: actions/checkout@v2 diff --git a/MANIFEST.in b/MANIFEST.in index 2968589b..4b487267 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include requirements.txt +include dev-requirements.txt include CMakeLists.txt graft pybind_interface diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 00000000..4cb3afc0 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,3 @@ +black==20.8b1 +flynt~=0.60 +pytest diff --git a/docs/install_qsimcirq.md b/docs/install_qsimcirq.md index 9ae35d21..0a0f3182 100644 --- a/docs/install_qsimcirq.md +++ b/docs/install_qsimcirq.md @@ -17,6 +17,12 @@ Prerequisites are included in the [`requirements.txt`](https://github.com/quantumlib/qsim/blob/master/requirements.txt) file, and will be automatically installed along with qsimcirq. +If you'd like to develop qsimcirq, a separate set of dependencies are includes +in the +[`dev-requirements.txt`](https://github.com/quantumlib/qsim/blob/master/dev-requirements.txt) +file. You can install them with `pip3 install -r dev-requirements.txt` or +`pip3 install qsimcirq[dev]`. + ## Linux installation We provide `qsimcirq` Python wheels on 64-bit `x86` architectures with `Python 3.{6,7,8,9}`. diff --git a/requirements.txt b/requirements.txt index 01d20cd5..0a95d64c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,5 @@ -# Runtime requirements for the python 3 version of cirq. - +absl-py cirq-core numpy~=1.16 -typing_extensions -absl-py - -# Build and test requirements - -black==20.8b1 -flynt~=0.60 pybind11 -pytest +typing_extensions diff --git a/setup.py b/setup.py index a6d76835..30bb72d4 100644 --- a/setup.py +++ b/setup.py @@ -78,6 +78,7 @@ def build_extension(self, ext): requirements = open("requirements.txt").readlines() +dev_requirements = open("dev-requirements.txt").readlines() description = "Schrödinger and Schrödinger-Feynman simulators for quantum circuits." @@ -94,6 +95,9 @@ def build_extension(self, ext): author_email="devabathini92@gmail.com", python_requires=">=3.3.0", install_requires=requirements, + extras_require={ + "dev": dev_requirements, + }, license="Apache 2", description=description, long_description=long_description, From 9cd89369755860c82b7fc0b29f737ce3e1661790 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 5 Nov 2021 08:28:11 -0700 Subject: [PATCH 142/246] Update cirq_compatibility.yml --- .github/workflows/cirq_compatibility.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cirq_compatibility.yml b/.github/workflows/cirq_compatibility.yml index 04e13559..f4fd7473 100644 --- a/.github/workflows/cirq_compatibility.yml +++ b/.github/workflows/cirq_compatibility.yml @@ -18,5 +18,7 @@ jobs: run: pip3 install -U cirq --pre - name: Install qsim requirements run: pip3 install -r requirements.txt + - name: Install test requirements + run: pip3 install -r dev-requirements.txt - name: Run python tests run: make run-py-tests From 99c1f699cf7ba8ad13bd0ec75a7bba3534f4a1d7 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 8 Nov 2021 08:52:33 -0800 Subject: [PATCH 143/246] Add cuQuantum blurb in docs --- docs/tutorials/gcp_gpu.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index 6d442051..234d6dd6 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -175,6 +175,20 @@ After a moment, you should see a result that looks similar to the following. [(0.7071067690849304+0j), 0j] ``` +### Optional: Use the NVIDIA cuQuantum SDK + +If you have the NVIDIA cuQuantum SDK installed, you can use it here by +modifying the `gpu_options` line like so: + +```python +gpu_options = qsimcirq.QSimOptions(use_gpu=True, gpu_mode=1) +``` + +This instructs qsim to make use of its cuQuantum integration, which provides +improved performance on NVIDIA GPUs. If you experience issues with this +option, please file an issue on the qsim repository. + + ## Next steps After you finish, don't forget to stop or delete your VM on the Compute From 3e96eef6b157c1efff08d7b7b8a2dbb67e6ced0f Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Mon, 8 Nov 2021 17:26:42 +0100 Subject: [PATCH 144/246] Add cuQuantum integration. --- CMakeLists.txt | 3 + Makefile | 16 + apps/Makefile | 15 +- apps/make.sh | 4 + apps/qsim_base_custatevec.cu | 171 ++++++++ docs/cirq_interface.md | 16 +- lib/simulator_custatevec.h | 182 +++++++++ lib/statespace_custatevec.h | 378 ++++++++++++++++++ lib/util_custatevec.h | 44 ++ pybind_interface/Makefile | 19 +- pybind_interface/cuda/pybind_main_cuda.cpp | 4 +- pybind_interface/custatevec/CMakeLists.txt | 58 +++ .../custatevec/pybind_main_custatevec.cpp | 58 +++ .../custatevec/pybind_main_custatevec.h | 17 + pybind_interface/decide/CMakeLists.txt | 7 + pybind_interface/decide/decide.cpp | 34 +- pybind_interface/pybind_main.cpp | 70 ++-- qsimcirq/__init__.py | 23 ++ qsimcirq/qsim_simulator.py | 37 +- qsimcirq_tests/qsimcirq_test.py | 83 ++++ setup.py | 1 + tests/Makefile | 19 +- tests/hybrid_custatevec_test.cu | 67 ++++ tests/make.sh | 8 + tests/qtrajectory_custatevec_test.cu | 83 ++++ tests/simulator_custatevec_test.cu | 108 +++++ tests/statespace_custatevec_test.cu | 127 ++++++ 27 files changed, 1601 insertions(+), 51 deletions(-) create mode 100644 apps/qsim_base_custatevec.cu create mode 100644 lib/simulator_custatevec.h create mode 100644 lib/statespace_custatevec.h create mode 100644 lib/util_custatevec.h create mode 100644 pybind_interface/custatevec/CMakeLists.txt create mode 100644 pybind_interface/custatevec/pybind_main_custatevec.cpp create mode 100644 pybind_interface/custatevec/pybind_main_custatevec.h create mode 100644 tests/hybrid_custatevec_test.cu create mode 100644 tests/qtrajectory_custatevec_test.cu create mode 100644 tests/simulator_custatevec_test.cu create mode 100644 tests/statespace_custatevec_test.cu diff --git a/CMakeLists.txt b/CMakeLists.txt index d1a6c0ea..615a33db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,9 @@ if(has_nvcc STREQUAL "") else() project(qsim LANGUAGES CXX CUDA) ADD_SUBDIRECTORY(pybind_interface/cuda) + if(DEFINED ENV{CUQUANTUM_ROOT}) + ADD_SUBDIRECTORY(pybind_interface/custatevec) + endif() endif() ADD_SUBDIRECTORY(pybind_interface/sse) diff --git a/Makefile b/Makefile index 49d1370b..900aa3cd 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,9 @@ CXXFLAGS = -O3 -fopenmp ARCHFLAGS = -march=native NVCCFLAGS = -O3 +# CUQUANTUM_ROOT should be set. +CUSTATEVECFLAGS = -I$(CUQUANTUM_ROOT)/include -L$(CUQUANTUM_ROOT)/lib64 -lcustatevec -lcublas + PYBIND11 = true export CXX @@ -18,6 +21,7 @@ export CXXFLAGS export ARCHFLAGS export NVCC export NVCCFLAGS +export CUSTATEVECFLAGS ifeq ($(PYBIND11), true) TARGETS += pybind @@ -35,6 +39,10 @@ qsim: qsim-cuda: $(MAKE) -C apps/ qsim-cuda +.PHONY: qsim-custatevec +qsim-custatevec: + $(MAKE) -C apps/ qsim-custatevec + .PHONY: pybind pybind: $(MAKE) -C pybind_interface/ pybind @@ -47,6 +55,10 @@ cxx-tests: eigen cuda-tests: $(MAKE) -C tests/ cuda-tests +.PHONY: custatevec-tests +custatevec-tests: + $(MAKE) -C tests/ custatevec-tests + .PHONY: run-cxx-tests run-cxx-tests: cxx-tests $(MAKE) -C tests/ run-cxx-tests @@ -55,6 +67,10 @@ run-cxx-tests: cxx-tests run-cuda-tests: cuda-tests $(MAKE) -C tests/ run-cuda-tests +.PHONY: run-custatevec-tests +run-custatevec-tests: custatevec-tests + $(MAKE) -C tests/ run-custatevec-tests + PYTESTS = $(shell find qsimcirq_tests/ -name '*_test.py') .PHONY: run-py-tests diff --git a/apps/Makefile b/apps/Makefile index cb16c1c9..41fb81e5 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -1,8 +1,11 @@ CXX_TARGETS = $(shell find . -maxdepth 1 -name '*.cc') CXX_TARGETS := $(CXX_TARGETS:%.cc=%.x) -CUDA_TARGETS = $(shell find . -maxdepth 1 -name '*.cu') -CUDA_TARGETS := $(CUDA_TARGETS:%.cu=%.x) +CUDA_TARGETS = $(shell find . -maxdepth 1 -name '*cuda.cu') +CUDA_TARGETS := $(CUDA_TARGETS:%cuda.cu=%cuda.x) + +CUSTATEVEC_TARGETS = $(shell find . -maxdepth 1 -name "*custatevec.cu") +CUSTATEVEC_TARGETS := $(CUSTATEVEC_TARGETS:%custatevec.cu=%custatevec.x) .PHONY: qsim qsim: $(CXX_TARGETS) @@ -10,12 +13,18 @@ qsim: $(CXX_TARGETS) .PHONY: qsim-cuda qsim-cuda: $(CUDA_TARGETS) +.PHONY: qsim-custatevec +qsim-custatevec: $(CUSTATEVEC_TARGETS) + %.x: %.cc $(CXX) -o ./$@ $< $(CXXFLAGS) $(ARCHFLAGS) -%.x: %.cu +%cuda.x: %cuda.cu $(NVCC) -o ./$@ $< $(NVCCFLAGS) +%custatevec.x: %custatevec.cu + $(NVCC) -o ./$@ $< $(NVCCFLAGS) $(CUSTATEVECFLAGS) + .PHONY: clean clean: -rm -f ./*.x ./*.a ./*.so ./*.mod diff --git a/apps/make.sh b/apps/make.sh index e21f410c..51052cc5 100755 --- a/apps/make.sh +++ b/apps/make.sh @@ -25,3 +25,7 @@ g++ -O3 -march=native -fopenmp -o qsimh_amplitudes.x qsimh_amplitudes.cc nvcc -O3 -o qsim_base_cuda.x qsim_base_cuda.cu nvcc -O3 -o qsim_qtrajectory_cuda.x qsim_qtrajectory_cuda.cu + +# CUQUANTUM_ROOT should be set. +CUSTATEVECFLAGS="-I${CUQUANTUM_ROOT}/include -L${CUQUANTUM_ROOT}/lib64 -lcustatevec -lcublas" +nvcc -O3 $CUSTATEVECFLAGS -o qsim_base_custatevec.x qsim_base_custatevec.cu diff --git a/apps/qsim_base_custatevec.cu b/apps/qsim_base_custatevec.cu new file mode 100644 index 00000000..a83f3e46 --- /dev/null +++ b/apps/qsim_base_custatevec.cu @@ -0,0 +1,171 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include +#include + +#include + +#include "../lib/circuit_qsim_parser.h" +#include "../lib/formux.h" +#include "../lib/fuser_mqubit.h" +#include "../lib/gates_qsim.h" +#include "../lib/io_file.h" +#include "../lib/run_qsim.h" +#include "../lib/simulator_custatevec.h" +#include "../lib/util_custatevec.h" + +struct Options { + std::string circuit_file; + unsigned maxtime = std::numeric_limits::max(); + unsigned seed = 1; + unsigned max_fused_size = 2; + unsigned verbosity = 0; +}; + +Options GetOptions(int argc, char* argv[]) { + constexpr char usage[] = "usage:\n ./qsim_base -c circuit -d maxtime " + "-s seed -f max_fused_size -v verbosity\n"; + + Options opt; + + int k; + + while ((k = getopt(argc, argv, "c:d:s:f:v:")) != -1) { + switch (k) { + case 'c': + opt.circuit_file = optarg; + break; + case 'd': + opt.maxtime = std::atoi(optarg); + break; + case 's': + opt.seed = std::atoi(optarg); + break; + case 'f': + opt.max_fused_size = std::atoi(optarg); + break; + case 'v': + opt.verbosity = std::atoi(optarg); + break; + default: + qsim::IO::errorf(usage); + exit(1); + } + } + + return opt; +} + +bool ValidateOptions(const Options& opt) { + if (opt.circuit_file.empty()) { + qsim::IO::errorf("circuit file is not provided.\n"); + return false; + } + + return true; +} + +template +void PrintAmplitudes( + unsigned num_qubits, const StateSpace& state_space, const State& state) { + static constexpr char const* bits[8] = { + "000", "001", "010", "011", "100", "101", "110", "111", + }; + + uint64_t size = std::min(uint64_t{8}, uint64_t{1} << num_qubits); + unsigned s = 3 - std::min(unsigned{3}, num_qubits); + + for (uint64_t i = 0; i < size; ++i) { + auto a = state_space.GetAmpl(state, i); + qsim::IO::messagef("%s:%16.8g%16.8g%16.8g\n", + bits[i] + s, std::real(a), std::imag(a), std::norm(a)); + } +} + +int main(int argc, char* argv[]) { + using namespace qsim; + + auto opt = GetOptions(argc, argv); + if (!ValidateOptions(opt)) { + return 1; + } + + using fp_type = float; + + Circuit> circuit; + if (!CircuitQsimParser::FromFile(opt.maxtime, opt.circuit_file, + circuit)) { + return 1; + } + + struct Factory { + using Simulator = qsim::SimulatorCuStateVec; + using StateSpace = Simulator::StateSpace; + + Factory() { + ErrorCheck(cublasCreate(&cublas_handle)); + ErrorCheck(custatevecCreate(&custatevec_handle)); + } + + ~Factory() { + ErrorCheck(cublasDestroy(cublas_handle)); + ErrorCheck(custatevecDestroy(custatevec_handle)); + } + + StateSpace CreateStateSpace() const { + return StateSpace(cublas_handle, custatevec_handle); + } + + Simulator CreateSimulator() const { + return Simulator(custatevec_handle); + } + + cublasHandle_t cublas_handle; + custatevecHandle_t custatevec_handle; + }; + + using Simulator = Factory::Simulator; + using StateSpace = Simulator::StateSpace; + using State = StateSpace::State; + using Fuser = MultiQubitGateFuser>; + using Runner = QSimRunner; + + Factory factory; + + StateSpace state_space = factory.CreateStateSpace(); + State state = state_space.Create(circuit.num_qubits); + + if (state_space.IsNull(state)) { + IO::errorf("not enough memory: is the number of qubits too large?\n"); + return 1; + } + + state_space.SetStateZero(state); + + Runner::Parameter param; + param.max_fused_size = opt.max_fused_size; + param.seed = opt.seed; + param.verbosity = opt.verbosity; + + if (Runner::Run(param, factory, circuit, state)) { + PrintAmplitudes(circuit.num_qubits, state_space, state); + } + + return 0; +} diff --git a/docs/cirq_interface.md b/docs/cirq_interface.md index 5184ed3d..8f610450 100644 --- a/docs/cirq_interface.md +++ b/docs/cirq_interface.md @@ -50,7 +50,7 @@ which invokes qsim through the qsim-Cirq interface. ## Interface design and operations The purpose of this interface is to provide a performant simulator for quantum -circuits defined in Cirq. +circuits defined in Cirq. ### Classes @@ -179,14 +179,18 @@ and run on a device with available NVIDIA GPUs. Compilation for GPU follows the same steps outlined in the [Compiling qsimcirq](./cirq_interface.md#compiling-qsimcirq) section. +To compile with the NVIDIA cuStateVec library, set the environmment variable +`CUQUANTUM_ROOT` to path to the root of the cuStateVec library. -`QSimOptions` provides four parameters to configure GPU execution. Only -`use_gpu` is required to enable GPU execution: +`QSimOptions` provides five parameters to configure GPU execution. `use_gpu` +is required to enable GPU execution: * `use_gpu`: if True, use GPU instead of CPU for simulation. +* `gpu_mode`: use CUDA if set to 0 (default value) or use the NVIDIA cuStateVec +library if set to any other value. -The remaining parameters use default values which provide good performance in -most cases, but can optionally be set to fine-tune perfomance for a specific -device or circuit. +If `use_gpu` is set and `gpu_mode` is set to 0, the remaining parameters can +optionally be set to fine-tune perfomance for a specific device or circuit. +In most cases, the default values provide good performance. * `gpu_sim_threads`: number of threads per CUDA block to use for the GPU Simulator. This must be a power of 2 in the range [32, 256]. * `gpu_state_threads`: number of threads per CUDA block to use for the GPU diff --git a/lib/simulator_custatevec.h b/lib/simulator_custatevec.h new file mode 100644 index 00000000..05e91e16 --- /dev/null +++ b/lib/simulator_custatevec.h @@ -0,0 +1,182 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SIMULATOR_CUSTATEVEC_H_ +#define SIMULATOR_CUSTATEVEC_H_ + +#include +#include +#include + +#include +#include + +#include "statespace_custatevec.h" +#include "util_custatevec.h" + +namespace qsim { + +/** + * Quantum circuit simulator using the NVIDIA cuStateVec library. + */ +template +class SimulatorCuStateVec final { + public: + using StateSpace = StateSpaceCuStateVec; + using State = typename StateSpace::State; + using fp_type = typename StateSpace::fp_type; + + static constexpr auto kStateType = StateSpace::kStateType; + static constexpr auto kMatrixType = StateSpace::kMatrixType; + static constexpr auto kExpectType = StateSpace::kExpectType; + static constexpr auto kComputeType = StateSpace::kComputeType; + static constexpr auto kMatrixLayout = StateSpace::kMatrixLayout; + + explicit SimulatorCuStateVec(const custatevecHandle_t& handle) + : handle_(handle), workspace_(nullptr), workspace_size_(0) {} + + ~SimulatorCuStateVec() { + ErrorCheck(cudaFree(workspace_)); + } + + /** + * Applies a gate using the NVIDIA cuStateVec library. + * @param qs Indices of the qubits affected by this gate. + * @param matrix Matrix representation of the gate to be applied. + * @param state The state of the system, to be updated by this method. + */ + void ApplyGate(const std::vector& qs, + const fp_type* matrix, State& state) const { + auto workspace_size = ApplyGateWorkSpaceSize( + state.num_qubits(), qs.size(), 0, matrix); + AllocWorkSpace(workspace_size); + + ErrorCheck(custatevecApplyMatrix( + handle_, state.get(), kStateType, state.num_qubits(), + matrix, kMatrixType, kMatrixLayout, 0, + (int32_t*) qs.data(), qs.size(), nullptr, 0, nullptr, + kComputeType, workspace_, workspace_size)); + } + + /** + * Applies a controlled gate using the NVIDIA cuStateVec library. + * @param qs Indices of the qubits affected by this gate. + * @param cqs Indices of control qubits. + * @param cmask Bit mask of control qubit values. + * @param matrix Matrix representation of the gate to be applied. + * @param state The state of the system, to be updated by this method. + */ + void ApplyControlledGate(const std::vector& qs, + const std::vector& cqs, uint64_t cmask, + const fp_type* matrix, State& state) const { + std::vector control_bits; + control_bits.reserve(cqs.size()); + + for (std::size_t i = 0; i < cqs.size(); ++i) { + control_bits.push_back((cmask >> i) & 1); + } + + auto workspace_size = ApplyGateWorkSpaceSize( + state.num_qubits(), qs.size(), cqs.size(), matrix); + AllocWorkSpace(workspace_size); + + ErrorCheck(custatevecApplyMatrix( + handle_, state.get(), kStateType, state.num_qubits(), + matrix, kMatrixType, kMatrixLayout, 0, + (int32_t*) qs.data(), qs.size(), + (int32_t*) cqs.data(), cqs.size(), control_bits.data(), + kComputeType, workspace_, workspace_size)); + } + + /** + * Computes the expectation value of an operator using the NVIDIA cuStateVec + * library. + * @param qs Indices of the qubits the operator acts on. + * @param matrix The operator matrix. + * @param state The state of the system. + * @return The computed expectation value. + */ + std::complex ExpectationValue(const std::vector& qs, + const fp_type* matrix, + const State& state) const { + auto workspace_size = ExpectationValueWorkSpaceSize( + state.num_qubits(), qs.size(), matrix); + AllocWorkSpace(workspace_size); + + cuDoubleComplex eval; + + ErrorCheck(custatevecExpectation( + handle_, state.get(), kStateType, state.num_qubits(), + &eval, kExpectType, nullptr, matrix, kMatrixType, + kMatrixLayout, (int32_t*) qs.data(), qs.size(), + kComputeType, workspace_, workspace_size)); + + return {cuCreal(eval), cuCimag(eval)}; + } + + /** + * @return The size of SIMD register if applicable. + */ + static unsigned SIMDRegisterSize() { + return 32; + } + + private: + size_t ApplyGateWorkSpaceSize( + unsigned num_qubits, unsigned num_targets, unsigned num_controls, + const fp_type* matrix) const { + size_t size; + + ErrorCheck(custatevecApplyMatrix_bufferSize( + handle_, kStateType, num_qubits, matrix, kMatrixType, + kMatrixLayout, 0, num_targets, num_controls, kComputeType, + &size)); + + return size; + } + + size_t ExpectationValueWorkSpaceSize( + unsigned num_qubits, unsigned num_targets, const fp_type* matrix) const { + size_t size; + + ErrorCheck(custatevecExpectation_bufferSize( + handle_, kStateType, num_qubits, matrix, kMatrixType, + kMatrixLayout, num_targets, kComputeType, &size)); + + return size; + } + + void* AllocWorkSpace(size_t size) const { + if (size > workspace_size_) { + if (workspace_ != nullptr) { + ErrorCheck(cudaFree(workspace_)); + } + + ErrorCheck(cudaMalloc(const_cast(&workspace_), size)); + + const_cast(workspace_size_) = size; + } + + return workspace_; + } + + const custatevecHandle_t handle_; + + void* workspace_; + size_t workspace_size_; +}; + +} // namespace qsim + +#endif // SIMULATOR_CUSTATEVEC_H_ diff --git a/lib/statespace_custatevec.h b/lib/statespace_custatevec.h new file mode 100644 index 00000000..dd2b4599 --- /dev/null +++ b/lib/statespace_custatevec.h @@ -0,0 +1,378 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STATESPACE_CUSTATEVEC_H_ +#define STATESPACE_CUSTATEVEC_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "statespace.h" +#include "util_custatevec.h" +#include "vectorspace_cuda.h" + +namespace qsim { + +namespace detail { + +template +__global__ void SetStateUniformKernel(FP v, uint64_t size, FP* state) { + uint64_t k = uint64_t{blockIdx.x} * blockDim.x + threadIdx.x; + + if (k < size) { + state[2 * k] = v; + state[2 * k + 1] = 0; + } +} + +} // namespace detail + +/** + * Object containing context and routines for cuStateVec state-vector + * manipulations. It is not recommended to use `GetAmpl` and `SetAmpl`. + */ +template +class StateSpaceCuStateVec : + public StateSpace, VectorSpaceCUDA, FP> { + private: + using Base = StateSpace, qsim::VectorSpaceCUDA, FP>; + + public: + using State = typename Base::State; + using fp_type = typename Base::fp_type; + + private: + static constexpr auto is_float = std::is_same::value; + + public: + static constexpr auto kStateType = is_float ? CUDA_C_32F : CUDA_C_64F; + static constexpr auto kMatrixType = kStateType; + static constexpr auto kExpectType = CUDA_C_64F; + static constexpr auto kComputeType = + is_float ? CUSTATEVEC_COMPUTE_32F : CUSTATEVEC_COMPUTE_64F; + static constexpr auto kMatrixLayout = CUSTATEVEC_MATRIX_LAYOUT_ROW; + + explicit StateSpaceCuStateVec(const cublasHandle_t& cublas_handle, + const custatevecHandle_t& custatevec_handle) + : cublas_handle_(cublas_handle), custatevec_handle_(custatevec_handle), + workspace_(nullptr), workspace_size_(0) {} + + virtual ~StateSpaceCuStateVec() { + if (workspace_ != nullptr) { + ErrorCheck(cudaFree(workspace_)); + } + } + + static uint64_t MinSize(unsigned num_qubits) { + return 2 * (uint64_t{1} << num_qubits); + }; + + void InternalToNormalOrder(State& state) const { + } + + void NormalToInternalOrder(State& state) const { + } + + void SetAllZeros(State& state) const { + ErrorCheck(cudaMemset(state.get(), 0, + MinSize(state.num_qubits()) * sizeof(fp_type))); + } + + // Uniform superposition. + void SetStateUniform(State& state) const { + uint64_t size = uint64_t{1} << state.num_qubits(); + + unsigned threads = size < 256 ? size : 256; + unsigned blocks = size / threads; + + fp_type v = double{1} / std::sqrt(size); + + detail::SetStateUniformKernel<<>>(v, size, state.get()); + ErrorCheck(cudaPeekAtLastError()); + } + + // |0> state. + void SetStateZero(State& state) const { + SetAllZeros(state); + fp_type one[1] = {1}; + ErrorCheck( + cudaMemcpy(state.get(), one, sizeof(fp_type), cudaMemcpyHostToDevice)); + } + + // It is not recommended to use this function. + static std::complex GetAmpl(const State& state, uint64_t i) { + fp_type a[2]; + auto p = state.get() + 2 * i; + ErrorCheck(cudaMemcpy(a, p, 2 * sizeof(fp_type), cudaMemcpyDeviceToHost)); + return std::complex(a[0], a[1]); + } + + // It is not recommended to use this function. + static void SetAmpl( + State& state, uint64_t i, const std::complex& ampl) { + fp_type a[2] = {std::real(ampl), std::imag(ampl)}; + auto p = state.get() + 2 * i; + ErrorCheck(cudaMemcpy(p, a, 2 * sizeof(fp_type), cudaMemcpyHostToDevice)); + } + + // It is not recommended to use this function. + static void SetAmpl(State& state, uint64_t i, fp_type re, fp_type im) { + fp_type a[2] = {re, im}; + auto p = state.get() + 2 * i; + ErrorCheck(cudaMemcpy(p, a, 2 * sizeof(fp_type), cudaMemcpyHostToDevice)); + } + + // Sets state[i] = complex(re, im) where (i & mask) == bits. + // if `exclude` is true then the criteria becomes (i & mask) != bits. + void BulkSetAmpl(State& state, uint64_t mask, uint64_t bits, + const std::complex& val, + bool exclude = false) const { + // Not implemented. + } + + // Sets state[i] = complex(re, im) where (i & mask) == bits. + // if `exclude` is true then the criteria becomes (i & mask) != bits. + void BulkSetAmpl(State& state, uint64_t mask, uint64_t bits, fp_type re, + fp_type im, bool exclude = false) const { + // Not implemented. + } + + // Does the equivalent of dest += src elementwise. + bool Add(const State& src, State& dest) const { + if (src.num_qubits() != dest.num_qubits()) { + return false; + } + + uint64_t size = uint64_t{1} << src.num_qubits(); + + if (is_float) { + cuComplex a = {1.0, 0.0}; + auto p1 = (const cuComplex*) src.get(); + auto p2 = (cuComplex*) dest.get(); + ErrorCheck(cublasCaxpy(cublas_handle_, size, &a, p1, 1, p2, 1)); + } else { + cuDoubleComplex a = {1.0, 0.0}; + auto p1 = (const cuDoubleComplex*) src.get(); + auto p2 = (cuDoubleComplex*) dest.get(); + ErrorCheck(cublasZaxpy(cublas_handle_, size, &a, p1, 1, p2, 1)); + } + + return true; + } + + // Does the equivalent of state *= a elementwise. + void Multiply(fp_type a, State& state) const { + uint64_t size = uint64_t{1} << state.num_qubits(); + + if (is_float) { + float a1 = a; + auto p = (cuComplex*) state.get(); + ErrorCheck(cublasCsscal(cublas_handle_, size, &a1, p, 1)); + } else { + double a1 = a; + auto p = (cuDoubleComplex*) state.get(); + ErrorCheck(cublasZdscal(cublas_handle_, size, &a1, p, 1)); + } + } + + std::complex InnerProduct( + const State& state1, const State& state2) const { + if (state1.num_qubits() != state2.num_qubits()) { + return std::nan(""); + } + + uint64_t size = uint64_t{1} << state1.num_qubits(); + + if (is_float) { + cuComplex result; + auto p1 = (const cuComplex*) state1.get(); + auto p2 = (const cuComplex*) state2.get(); + ErrorCheck(cublasCdotc(cublas_handle_, size, p1, 1, p2, 1, &result)); + return {cuCrealf(result), cuCimagf(result)}; + } else { + cuDoubleComplex result; + auto p1 = (const cuDoubleComplex*) state1.get(); + auto p2 = (const cuDoubleComplex*) state2.get(); + ErrorCheck(cublasZdotc(cublas_handle_, size, p1, 1, p2, 1, &result)); + return {cuCreal(result), cuCimag(result)}; + } + } + + double RealInnerProduct(const State& state1, const State& state2) const { + return std::real(InnerProduct(state1, state2)); + } + + double Norm(const State& state) const { + uint64_t size = uint64_t{1} << state.num_qubits(); + + if (is_float) { + float result; + auto p = (const cuComplex*) state.get(); + ErrorCheck(cublasScnrm2(cublas_handle_, size, p, 1, &result)); + return result * result; + } else { + double result; + auto p = (const cuDoubleComplex*) state.get(); + ErrorCheck(cublasDznrm2(cublas_handle_, size, p, 1, &result)); + return result * result; + } + } + + template + std::vector Sample( + const State& state, uint64_t num_samples, unsigned seed) const { + std::vector bitstrings; + + if (num_samples > 0) { + auto rs = GenerateRandomValues(num_samples, seed, 1.0); + + size_t workspace_size; + custatevecSamplerDescriptor_t sampler; + + ErrorCheck(custatevecSampler_create( + custatevec_handle_, state.get(), kStateType, + state.num_qubits(), &sampler, num_samples, + &workspace_size)); + + AllocWorkSpace(workspace_size); + + ErrorCheck(custatevecSampler_preprocess( + custatevec_handle_, &sampler, workspace_, workspace_size)); + + std::vector bitstrings0(num_samples); + std::vector bitordering; + + bitordering.reserve(state.num_qubits()); + for (unsigned i = 0; i < state.num_qubits(); ++i) { + bitordering.push_back(i); + } + + ErrorCheck(custatevecSampler_sample( + custatevec_handle_, &sampler, bitstrings0.data(), + bitordering.data(), state.num_qubits(), rs.data(), + num_samples, CUSTATEVEC_SAMPLER_OUTPUT_RANDNUM_ORDER)); + + bitstrings.reserve(num_samples); + for (unsigned i = 0; i < num_samples; ++i) { + bitstrings.push_back(bitstrings0[i]); + } + } + + return bitstrings; + } + + using MeasurementResult = typename Base::MeasurementResult; + + template + MeasurementResult Measure(const std::vector& qubits, + RGen& rgen, State& state, + bool no_collapse = false) const { + auto r = RandomValue(rgen, 1.0); + + MeasurementResult result; + + result.valid = true; + result.mask = 0; + result.bits = 0; + result.bitstring.resize(qubits.size(), 0); + + for (auto q : qubits) { + if (q >= state.num_qubits()) { + result.valid = false; + return result; + } + + result.mask |= uint64_t{1} << q; + } + + auto collapse = no_collapse ? + CUSTATEVEC_COLLAPSE_NONE : CUSTATEVEC_COLLAPSE_NORMALIZE_AND_ZERO; + + ErrorCheck(custatevecBatchMeasure( + custatevec_handle_, state.get(), kStateType, + state.num_qubits(), (int*) result.bitstring.data(), + (int*) qubits.data(), qubits.size(), r, collapse)); + + for (std::size_t i = 0; i < result.bitstring.size(); ++i) { + result.bits |= result.bitstring[i] << qubits[i]; + } + + return result; + } + + template + MeasurementResult VirtualMeasure(const std::vector& qubits, + RGen& rgen, const State& state) const { + return Measure(qubits, rgen, const_cast(state), true); + } + + void Collapse(const MeasurementResult& mr, State& state) const { + unsigned count = 0; + + std::vector bitstring; + std::vector bitordering; + + bitstring.reserve(state.num_qubits()); + bitordering.reserve(state.num_qubits()); + + for (unsigned i = 0; i < state.num_qubits(); ++i) { + if (((mr.mask >> i) & 1) != 0) { + bitstring.push_back((mr.bits >> i) & 1); + bitordering.push_back(i); + ++count; + } + } + + ErrorCheck(custatevecCollapseByBitString( + custatevec_handle_, state.get(), kStateType, + state.num_qubits(), bitstring.data(), bitordering.data(), + count, 1.0)); + + // TODO: do we need the following? + double norm = Norm(state); + Multiply(1.0 / std::sqrt(norm), state); + } + + private: + void* AllocWorkSpace(size_t size) const { + if (size > workspace_size_) { + if (workspace_ != nullptr) { + ErrorCheck(cudaFree(workspace_)); + } + + ErrorCheck(cudaMalloc(const_cast(&workspace_), size)); + + const_cast(workspace_size_) = size; + } + + return workspace_; + } + + const cublasHandle_t cublas_handle_; + const custatevecHandle_t custatevec_handle_; + + void* workspace_; + size_t workspace_size_; +}; + +} // namespace qsim + +#endif // STATESPACE_CUSTATEVEC_H_ diff --git a/lib/util_custatevec.h b/lib/util_custatevec.h new file mode 100644 index 00000000..36f29efa --- /dev/null +++ b/lib/util_custatevec.h @@ -0,0 +1,44 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef UTIL_CUSTATEVEC_H_ +#define UTIL_CUSTATEVEC_H_ + +#include +#include + +#include "io.h" +#include "util_cuda.h" + +namespace qsim { + +inline void ErrorAssert(cublasStatus_t code, const char* file, unsigned line) { + if (code != CUBLAS_STATUS_SUCCESS) { + IO::errorf("cuBLAS error %i: %s %d\n", code, file, line); + exit(code); + } +} + +inline void ErrorAssert( + custatevecStatus_t code, const char* file, unsigned line) { + if (code != CUSTATEVEC_STATUS_SUCCESS) { + IO::errorf("custatevec error: %s %s %d\n", + custatevecGetErrorString(code), file, line); + exit(code); + } +} + +} // namespace qsim + +#endif // UTIL_CUSTATEVEC_H_ diff --git a/pybind_interface/Makefile b/pybind_interface/Makefile index daebea5e..c8944562 100644 --- a/pybind_interface/Makefile +++ b/pybind_interface/Makefile @@ -4,9 +4,9 @@ QSIMLIB_SSE = ../qsimcirq/qsim_sse`python3-config --extension-suffix` QSIMLIB_AVX2 = ../qsimcirq/qsim_avx2`python3-config --extension-suffix` QSIMLIB_AVX512 = ../qsimcirq/qsim_avx512`python3-config --extension-suffix` QSIMLIB_CUDA = ../qsimcirq/qsim_cuda`python3-config --extension-suffix` +QSIMLIB_CUSTATEVEC = ../qsimcirq/qsim_custatevec`python3-config --extension-suffix` QSIMLIB_DECIDE = ../qsimcirq/qsim_decide`python3-config --extension-suffix` - # The flags for the compilation of the simd-specific Pybind11 interfaces PYBINDFLAGS_BASIC = -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` PYBINDFLAGS_SSE = -msse4.1 -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` @@ -16,11 +16,19 @@ PYBINDFLAGS_AVX512 = -mavx512f -Wall -shared -std=c++17 -fPIC `python3 -m pybind # The flags for the compilation of GPU-specific Pybind11 interfaces PYBINDFLAGS_CUDA = -std=c++14 -x cu -Xcompiler "-Wall -shared -fPIC `python3 -m pybind11 --includes`" +# The flags for the compilation of cuStateVec-specific Pybind11 interfaces +PYBINDFLAGS_CUSTATEVEC = $(CUSTATEVECFLAGS) $(PYBINDFLAGS_CUDA) + # Check for nvcc to decide compilation mode. ifeq ($(shell which $(NVCC)),) pybind: pybind-cpu decide-cpu else +# Check for the cuStateVec library. +ifeq ($(CUQUANTUM_ROOT),) pybind: pybind-cpu pybind-gpu decide-gpu +else +pybind: pybind-cpu pybind-gpu pybind-custatevec decide-custatevec +endif endif .PHONY: pybind-cpu @@ -42,6 +50,14 @@ pybind-gpu: decide-gpu: $(NVCC) decide/decide.cpp -o $(QSIMLIB_DECIDE) $(NVCCFLAGS) $(PYBINDFLAGS_CUDA) +.PHONY: pybind-custatevec +pybind-custatevec: + $(NVCC) custatevec/pybind_main_custatevec.cpp -o $(QSIMLIB_CUSTATEVEC) $(NVCCFLAGS) $(PYBINDFLAGS_CUSTATEVEC) + +.PHONY: decide-custatevec +decide-custatevec: + $(NVCC) decide/decide.cpp -D__CUSTATEVEC__ -o $(QSIMLIB_DECIDE) $(NVCCFLAGS) $(PYBINDFLAGS_CUDA) + .PHONY: clean clean: -rm -f ./basic/*.x ./basic/*.a ./basic/*.so ./basic/*.mod $(QSIMLIB_BASIC) @@ -49,4 +65,5 @@ clean: -rm -f ./avx2/*.x ./avx2/*.a ./avx2/*.so ./avx2/*.mod $(QSIMLIB_AVX2) -rm -f ./avx512/*.x ./avx512/*.a ./avx512/*.so ./avx512/*.mod $(QSIMLIB_AVX512) -rm -f ./cuda/*.x ./cuda/*.a ./cuda/*.so ./cuda/*.mod $(QSIMLIB_CUDA) + -rm -f ./custatevec/*.x ./custatevec/*.a ./custatevec/*.so ./custatevec/*.mod $(QSIMLIB_CUSTATEVEC) -rm -f ./decide/*.x ./decide/*.a ./decide/*.so ./decide/*.mod $(QSIMLIB_DECIDE) diff --git a/pybind_interface/cuda/pybind_main_cuda.cpp b/pybind_interface/cuda/pybind_main_cuda.cpp index 825082f5..391cb976 100644 --- a/pybind_interface/cuda/pybind_main_cuda.cpp +++ b/pybind_interface/cuda/pybind_main_cuda.cpp @@ -38,8 +38,8 @@ namespace qsim { return Simulator(sim_params); } - const StateSpace::Parameter ss_params; - const Simulator::Parameter sim_params; + StateSpace::Parameter ss_params; + Simulator::Parameter sim_params; }; } diff --git a/pybind_interface/custatevec/CMakeLists.txt b/pybind_interface/custatevec/CMakeLists.txt new file mode 100644 index 00000000..4ea57380 --- /dev/null +++ b/pybind_interface/custatevec/CMakeLists.txt @@ -0,0 +1,58 @@ +# Copyright 2019 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.11) +project(qsim LANGUAGES CXX CUDA) + +IF (WIN32) + set(CMAKE_CXX_FLAGS "/O2 /openmp") +ELSE() + set(CMAKE_CXX_FLAGS "-O3 -fopenmp") +ENDIF() + +if(APPLE) + set(CMAKE_CXX_STANDARD 14) + include_directories("/usr/local/include" "/usr/local/opt/llvm/include") + link_directories("/usr/local/lib" "/usr/local/opt/llvm/lib") +endif() + +include(FetchContent) + +FetchContent_Declare( + pybind11 + GIT_REPOSITORY https://github.com/pybind/pybind11 + GIT_TAG v2.2.4 +) +FetchContent_GetProperties(pybind11) +if(NOT pybind11_POPULATED) + FetchContent_Populate(pybind11) + add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) +endif() + +find_package(PythonLibs 3.6 REQUIRED) +find_package(CUDA REQUIRED) + +include_directories(${PYTHON_INCLUDE_DIRS} ${pybind11_SOURCE_DIR}/include) + +include_directories($ENV{CUQUANTUM_ROOT}/include) +link_directories($ENV{CUQUANTUM_ROOT}/lib64) + +cuda_add_library(qsim_custatevec MODULE pybind_main_custatevec.cpp) +target_link_libraries(qsim_custatevec -lcustatevec -lcublas) + +set_target_properties(qsim_custatevec PROPERTIES + PREFIX "${PYTHON_MODULE_PREFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}" +) +set_source_files_properties(pybind_main_custatevec.cpp PROPERTIES LANGUAGE CUDA) diff --git a/pybind_interface/custatevec/pybind_main_custatevec.cpp b/pybind_interface/custatevec/pybind_main_custatevec.cpp new file mode 100644 index 00000000..109cedae --- /dev/null +++ b/pybind_interface/custatevec/pybind_main_custatevec.cpp @@ -0,0 +1,58 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "pybind_main_custatevec.h" + +#include "../../lib/simulator_custatevec.h" + +namespace qsim { + +using Simulator = SimulatorCuStateVec; + +struct Factory { + using Simulator = qsim::Simulator; + using StateSpace = Simulator::StateSpace; + + // num_sim_threads, num_state_threads and num_dblocks are unused, but kept + // for consistency with other factories. + Factory(unsigned num_sim_threads, + unsigned num_state_threads, + unsigned num_dblocks) { + ErrorCheck(cublasCreate(&cublas_handle)); + ErrorCheck(custatevecCreate(&custatevec_handle)); + } + + ~Factory() { + ErrorCheck(cublasDestroy(cublas_handle)); + ErrorCheck(custatevecDestroy(custatevec_handle)); + } + + StateSpace CreateStateSpace() const { + return StateSpace(cublas_handle, custatevec_handle); + } + + Simulator CreateSimulator() const { + return Simulator(custatevec_handle); + } + + cublasHandle_t cublas_handle; + custatevecHandle_t custatevec_handle; +}; + +} + +#include "../pybind_main.cpp" diff --git a/pybind_interface/custatevec/pybind_main_custatevec.h b/pybind_interface/custatevec/pybind_main_custatevec.h new file mode 100644 index 00000000..b6722bf4 --- /dev/null +++ b/pybind_interface/custatevec/pybind_main_custatevec.h @@ -0,0 +1,17 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "../pybind_main.h" + +PYBIND11_MODULE(qsim_custatevec, m) { GPU_MODULE_BINDINGS } diff --git a/pybind_interface/decide/CMakeLists.txt b/pybind_interface/decide/CMakeLists.txt index 3e9fcbb2..99a75881 100644 --- a/pybind_interface/decide/CMakeLists.txt +++ b/pybind_interface/decide/CMakeLists.txt @@ -41,6 +41,13 @@ else() include_directories(${PYTHON_INCLUDE_DIRS} ${pybind11_SOURCE_DIR}/include) cuda_add_library(qsim_decide MODULE decide.cpp) + + if(DEFINED ENV{CUQUANTUM_ROOT}) + target_compile_options(qsim_decide PRIVATE + $<$:-D__CUSTATEVEC__> + ) + endif() + set_target_properties(qsim_decide PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" SUFFIX "${PYTHON_MODULE_EXTENSION}" diff --git a/pybind_interface/decide/decide.cpp b/pybind_interface/decide/decide.cpp index 890f7a41..6355ad3b 100644 --- a/pybind_interface/decide/decide.cpp +++ b/pybind_interface/decide/decide.cpp @@ -1,3 +1,17 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include namespace py = pybind11; @@ -45,7 +59,8 @@ int detect_instructions() { return static_cast(instr); } -enum GPUCapabilities { CUDA = 0, NO_GPU = 10 }; +enum GPUCapabilities { + CUDA = 0, CUSTATEVEC = 1, NO_GPU = 10, NO_CUSTATEVEC = 11 }; // For now, GPU detection is performed at compile time, as our wheels are // generated on Github Actions runners which do not have GPU support. @@ -61,6 +76,20 @@ int detect_gpu() { return gpu; } +// For now, cuStateVec detection is performed at compile time, as our wheels +// are generated on Github Actions runners which do not have GPU support. +// +// Users wishing to use qsim with cuStateVec will need to compile locally on +// a device which has the necessary CUDA toolkit and cuStateVec library. +int detect_custatevec() { + #if defined(__NVCC__) && defined(__CUSTATEVEC__) + GPUCapabilities gpu = CUSTATEVEC; + #else + GPUCapabilities gpu = NO_CUSTATEVEC; + #endif + return gpu; +} + PYBIND11_MODULE(qsim_decide, m) { m.doc() = "pybind11 plugin"; // optional module docstring @@ -69,4 +98,7 @@ PYBIND11_MODULE(qsim_decide, m) { // Detect available GPUs. m.def("detect_gpu", &detect_gpu, "Detect GPU"); + + // Detect cuStateVec. + m.def("detect_custatevec", &detect_custatevec, "Detect cuStateVec"); } diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 67a74cae..428372d1 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -384,19 +384,21 @@ std::vector> qsim_simulate(const py::dict &options) { bool use_gpu; bool denormals_are_zeros; - unsigned num_sim_threads; + unsigned gpu_mode; + unsigned num_sim_threads = 0; unsigned num_state_threads = 0; unsigned num_dblocks = 0; Runner::Parameter param; try { use_gpu = parseOptions(options, "g\0"); + gpu_mode = parseOptions(options, "gmode\0"); denormals_are_zeros = parseOptions(options, "z\0"); - if (use_gpu) { + if (use_gpu == 0) { + num_sim_threads = parseOptions(options, "t\0"); + } else if (gpu_mode == 0) { num_sim_threads = parseOptions(options, "gsmt\0"); num_state_threads = parseOptions(options, "gsst\0"); num_dblocks = parseOptions(options, "gdb\0"); - } else { - num_sim_threads = parseOptions(options, "t\0"); } param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); @@ -446,20 +448,22 @@ std::vector> qtrajectory_simulate(const py::dict &options) { Runner::Parameter param; bool use_gpu; bool denormals_are_zeros; - unsigned num_sim_threads; + unsigned gpu_mode; + unsigned num_sim_threads = 0; unsigned num_state_threads = 0; unsigned num_dblocks = 0; uint64_t seed; try { use_gpu = parseOptions(options, "g\0"); + gpu_mode = parseOptions(options, "gmode\0"); denormals_are_zeros = parseOptions(options, "z\0"); - if (use_gpu) { + if (use_gpu == 0) { + num_sim_threads = parseOptions(options, "t\0"); + } else if (gpu_mode == 0) { num_sim_threads = parseOptions(options, "gsmt\0"); num_state_threads = parseOptions(options, "gsst\0"); num_dblocks = parseOptions(options, "gdb\0"); - } else { - num_sim_threads = parseOptions(options, "t\0"); } param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); @@ -499,7 +503,7 @@ std::vector> qtrajectory_simulate(const py::dict &options) { class SimulatorHelper { public: using Simulator = Factory::Simulator; - using StateSpace = Simulator::StateSpace; + using StateSpace = Factory::StateSpace; using State = StateSpace::State; using Gate = Cirq::GateCirq; @@ -558,7 +562,7 @@ class SimulatorHelper { private: SimulatorHelper(const py::dict &options, bool noisy) - : state_space(Factory(1, 1, 1).CreateStateSpace()), + : factory(Factory(1, 1, 1)), state(StateSpace::Null()), scratch(StateSpace::Null()) { bool denormals_are_zeros; @@ -575,20 +579,24 @@ class SimulatorHelper { } use_gpu = parseOptions(options, "g\0"); + gpu_mode = parseOptions(options, "gmode\0"); denormals_are_zeros = parseOptions(options, "z\0"); - if (use_gpu) { + if (use_gpu == 0) { + num_sim_threads = parseOptions(options, "t\0"); + } else if (gpu_mode == 0) { num_sim_threads = parseOptions(options, "gsmt\0"); num_state_threads = parseOptions(options, "gsst\0"); num_dblocks = parseOptions(options, "gdb\0"); - } else { - num_sim_threads = parseOptions(options, "t\0"); } max_fused_size = parseOptions(options, "f\0"); verbosity = parseOptions(options, "v\0"); seed = parseOptions(options, "s\0"); - state_space = Factory( - num_sim_threads, num_state_threads, num_dblocks).CreateStateSpace(); + if (use_gpu == 0 || gpu_mode == 0) { + factory = Factory(num_sim_threads, num_state_threads, num_dblocks); + } + + StateSpace state_space = factory.CreateStateSpace(); state = state_space.Create(num_qubits); is_valid = true; @@ -604,11 +612,13 @@ class SimulatorHelper { } void init_state(uint64_t input_state) { + StateSpace state_space = factory.CreateStateSpace(); state_space.SetAllZeros(state); state_space.SetAmpl(state, input_state, 1, 0); } void init_state(const py::array_t &input_vector) { + StateSpace state_space = factory.CreateStateSpace(); state_space.Copy(input_vector.data(), state); state_space.NormalToInternalOrder(state); } @@ -633,8 +643,6 @@ class SimulatorHelper { init_state(input_state); bool result = false; - Factory factory(num_sim_threads, num_state_threads, num_dblocks); - if (is_noisy) { std::vector stat; auto params = get_noisy_params(); @@ -652,6 +660,7 @@ class SimulatorHelper { } py::array_t release_state_to_python() { + StateSpace state_space = factory.CreateStateSpace(); state_space.InternalToNormalOrder(state); uint64_t fsv_size = 2 * (uint64_t{1} << num_qubits); if (state.requires_copy_to_host()) { @@ -672,8 +681,8 @@ class SimulatorHelper { std::vector> get_expectation_value( const std::vector>, unsigned>>& opsums_and_qubit_counts) { - Simulator simulator = Factory( - num_sim_threads, num_state_threads, num_dblocks).CreateSimulator(); + Simulator simulator = factory.CreateSimulator(); + StateSpace state_space = factory.CreateStateSpace(); using Fuser = MultiQubitGateFuser; std::vector> results; @@ -697,11 +706,12 @@ class SimulatorHelper { Circuit circuit; NoisyCircuit ncircuit; - StateSpace state_space; + Factory factory; State state; State scratch; bool use_gpu; + unsigned gpu_mode; unsigned num_qubits; unsigned num_sim_threads; unsigned num_state_threads; @@ -799,19 +809,21 @@ std::vector qsim_sample(const py::dict &options) { bool use_gpu; bool denormals_are_zeros; - unsigned num_sim_threads; + unsigned gpu_mode; + unsigned num_sim_threads = 0; unsigned num_state_threads = 0; unsigned num_dblocks = 0; Runner::Parameter param; try { use_gpu = parseOptions(options, "g\0"); + gpu_mode = parseOptions(options, "gmode\0"); denormals_are_zeros = parseOptions(options, "z\0"); - if (use_gpu) { + if (use_gpu == 0) { + num_sim_threads = parseOptions(options, "t\0"); + } else if (gpu_mode == 0) { num_sim_threads = parseOptions(options, "gsmt\0"); num_state_threads = parseOptions(options, "gsst\0"); num_dblocks = parseOptions(options, "gdb\0"); - } else { - num_sim_threads = parseOptions(options, "t\0"); } param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); @@ -865,20 +877,22 @@ std::vector qtrajectory_sample(const py::dict &options) { Runner::Parameter param; bool use_gpu; bool denormals_are_zeros; - unsigned num_sim_threads; + unsigned gpu_mode; + unsigned num_sim_threads = 0; unsigned num_state_threads = 0; unsigned num_dblocks = 0; uint64_t seed; try { use_gpu = parseOptions(options, "g\0"); + gpu_mode = parseOptions(options, "gmode\0"); denormals_are_zeros = parseOptions(options, "z\0"); - if (use_gpu) { + if (use_gpu == 0) { + num_sim_threads = parseOptions(options, "t\0"); + } else if (gpu_mode == 0) { num_sim_threads = parseOptions(options, "gsmt\0"); num_state_threads = parseOptions(options, "gsst\0"); num_dblocks = parseOptions(options, "gdb\0"); - } else { - num_sim_threads = parseOptions(options, "t\0"); } param.max_fused_size = parseOptions(options, "f\0"); param.verbosity = parseOptions(options, "v\0"); diff --git a/qsimcirq/__init__.py b/qsimcirq/__init__.py index f1542018..8b7db6ba 100644 --- a/qsimcirq/__init__.py +++ b/qsimcirq/__init__.py @@ -1,3 +1,18 @@ +# Copyright 2019 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + import importlib from qsimcirq import qsim_decide @@ -23,9 +38,17 @@ def _load_qsim_gpu(): qsim_gpu = None return qsim_gpu +def _load_qsim_custatevec(): + instr = qsim_decide.detect_custatevec() + if instr == 1: + qsim_custatevec = importlib.import_module("qsimcirq.qsim_custatevec") + else: + qsim_custatevec = None + return qsim_custatevec qsim = _load_simd_qsim() qsim_gpu = _load_qsim_gpu() +qsim_custatevec = _load_qsim_custatevec() from .qsim_circuit import add_op_to_opstring, add_op_to_circuit, QSimCircuit from .qsim_simulator import ( diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 39796b92..d24eda47 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -34,7 +34,7 @@ import numpy as np -from . import qsim, qsim_gpu +from . import qsim, qsim_gpu, qsim_custatevec import qsimcirq.qsim_circuit as qsimc @@ -136,6 +136,9 @@ class QSimOptions: simulation modes. use_gpu: whether to use GPU instead of CPU for simulation. The "gpu_*" arguments below are only considered if this is set to True. + gpu_mode: use CUDA if set to 0 (default value) or use the NVIDIA + cuStateVec library if set to any other value. The "gpu_*" + arguments below are only considered if this is set to 0. gpu_sim_threads: number of threads per CUDA block to use for the GPU Simulator. This must be a power of 2 in the range [32, 256]. gpu_state_threads: number of threads per CUDA block to use for the GPU @@ -152,6 +155,7 @@ class QSimOptions: cpu_threads: int = 1 ev_noisy_repetitions: int = 1 use_gpu: bool = False + gpu_mode: int = 0 gpu_sim_threads: int = 256 gpu_state_threads: int = 512 gpu_data_blocks: int = 16 @@ -168,6 +172,7 @@ def as_dict(self): "t": self.cpu_threads, "r": self.ev_noisy_repetitions, "g": self.use_gpu, + "gmode": self.gpu_mode, "gsmt": self.gpu_sim_threads, "gsst": self.gpu_state_threads, "gdb": self.gpu_data_blocks, @@ -220,13 +225,31 @@ def __init__( self._prng = value.parse_random_state(seed) self.qsim_options = QSimOptions().as_dict() self.qsim_options.update(qsim_options) + # module to use for simulation - if self.qsim_options["g"] and qsim_gpu is None: - raise ValueError( - "GPU execution requested, but not supported. If your device " - "has GPU support, you may need to compile qsim locally." - ) - self._sim_module = qsim_gpu if self.qsim_options["g"] else qsim + if self.qsim_options["g"]: + if self.qsim_options["gmode"] == 0: + if qsim_gpu is None: + raise ValueError( + "GPU execution requested, but not supported. If your " + "device has GPU support, you may need to compile qsim " + "locally." + ) + else: + self._sim_module = qsim_gpu + else: + if qsim_custatevec is None: + raise ValueError( + "cuStateVec GPU execution requested, but not " + "supported. If your device has GPU support and the " + "NVIDIA cuStateVec library is installed, you may need " + "to compile qsim locally." + ) + else: + self._sim_module = qsim_custatevec + else: + self._sim_module = qsim + # Deque of (, ) tuples. self._translated_circuits = deque(maxlen=circuit_memoization_size) diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index aa63846e..77e0597f 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -1162,6 +1162,89 @@ def test_cirq_qsim_gpu_input_state(): ) +def test_cirq_qsim_custatevec_amplitudes(): + if qsimcirq.qsim_custatevec is None: + pytest.skip("cuStateVec library is not available for testing.") + # Pick qubits. + a, b = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] + + # Create a circuit + cirq_circuit = cirq.Circuit(cirq.CNOT(a, b), cirq.CNOT(b, a), cirq.X(a)) + + # Enable GPU acceleration. + custatevec_options = qsimcirq.QSimOptions(gpu_mode=1) + qsimGpuSim = qsimcirq.QSimSimulator(qsim_options=custatevec_options) + result = qsimGpuSim.compute_amplitudes( + cirq_circuit, bitstrings=[0b00, 0b01, 0b10, 0b11] + ) + assert np.allclose(result, [0j, 0j, (1 + 0j), 0j]) + + +def test_cirq_qsim_custatevec_simulate(): + if qsimcirq.qsim_custatevec is None: + pytest.skip("cuStateVec library is not available for testing.") + # Pick qubits. + a, b = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] + + # Create a circuit + cirq_circuit = cirq.Circuit(cirq.H(a), cirq.CNOT(a, b), cirq.X(b)) + + # Enable GPU acceleration. + custatevec_options = qsimcirq.QSimOptions(gpu_mode=1) + qsimGpuSim = qsimcirq.QSimSimulator(qsim_options=custatevec_options) + result = qsimGpuSim.simulate(cirq_circuit) + assert result.state_vector().shape == (4,) + + cirqSim = cirq.Simulator() + cirq_result = cirqSim.simulate(cirq_circuit) + assert cirq.linalg.allclose_up_to_global_phase( + result.state_vector(), cirq_result.state_vector(), atol=1.0e-6 + ) + + +def test_cirq_qsim_custatevec_expectation_values(): + if qsimcirq.qsim_custatevec is None: + pytest.skip("cuStateVec library is not available for testing.") + # Pick qubits. + a, b = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] + + # Create a circuit + cirq_circuit = cirq.Circuit(cirq.H(a), cirq.CNOT(a, b), cirq.X(b)) + obs = [cirq.Z(a) * cirq.Z(b)] + + # Enable GPU acceleration. + custatevec_options = qsimcirq.QSimOptions(gpu_mode=1) + qsimGpuSim = qsimcirq.QSimSimulator(qsim_options=custatevec_options) + result = qsimGpuSim.simulate_expectation_values(cirq_circuit, obs) + + cirqSim = cirq.Simulator() + cirq_result = cirqSim.simulate_expectation_values(cirq_circuit, obs) + assert np.allclose(result, cirq_result) + + +def test_cirq_qsim_custatevec_input_state(): + if qsimcirq.qsim_custatevec is None: + pytest.skip("cuStateVec library is not available for testing.") + # Pick qubits. + a, b = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)] + + # Create a circuit + cirq_circuit = cirq.Circuit(cirq.H(a), cirq.CNOT(a, b), cirq.X(b)) + + # Enable GPU acceleration. + custatevec_options = qsimcirq.QSimOptions(gpu_mode=1) + qsimGpuSim = qsimcirq.QSimSimulator(qsim_options=custatevec_options) + initial_state = np.asarray([0.5] * 4, dtype=np.complex64) + result = qsimGpuSim.simulate(cirq_circuit, initial_state=initial_state) + assert result.state_vector().shape == (4,) + + cirqSim = cirq.Simulator() + cirq_result = cirqSim.simulate(cirq_circuit, initial_state=initial_state) + assert cirq.linalg.allclose_up_to_global_phase( + result.state_vector(), cirq_result.state_vector(), atol=1.0e-6 + ) + + def test_cirq_qsim_old_options(): old_options = {"f": 3, "t": 4, "r": 100, "v": 1} old_sim = qsimcirq.QSimSimulator(qsim_options=old_options) diff --git a/setup.py b/setup.py index 30bb72d4..e1e80a70 100644 --- a/setup.py +++ b/setup.py @@ -108,6 +108,7 @@ def build_extension(self, ext): CMakeExtension("qsimcirq/qsim_sse"), CMakeExtension("qsimcirq/qsim_basic"), CMakeExtension("qsimcirq/qsim_cuda"), + CMakeExtension("qsimcirq/qsim_custatevec"), CMakeExtension("qsimcirq/qsim_decide"), ], cmdclass=dict(build_ext=CMakeBuild), diff --git a/tests/Makefile b/tests/Makefile index d10e887b..f4a37278 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -8,8 +8,11 @@ CXX_TARGETS = $(shell\ ) CXX_TARGETS := $(CXX_TARGETS:%.cc=%.x) -CUDA_TARGETS = $(shell find . -maxdepth 1 -name "*_test.cu") -CUDA_TARGETS := $(CUDA_TARGETS:%.cu=%.x) +CUDA_TARGETS = $(shell find . -maxdepth 1 -name "*cuda_test.cu") +CUDA_TARGETS := $(CUDA_TARGETS:%cuda_test.cu=%cuda_test.x) + +CUSTATEVEC_TARGETS = $(shell find . -maxdepth 1 -name "*custatevec_test.cu") +CUSTATEVEC_TARGETS := $(CUSTATEVEC_TARGETS:%custatevec_test.cu=%custatevec_test.x) GTEST_DIR = $(CURDIR)/googletest/googletest GMOCK_DIR = $(CURDIR)/googletest/googlemock @@ -24,6 +27,9 @@ cxx-tests: $(CXX_TARGETS) .PHONY: cuda-tests cuda-tests: $(CUDA_TARGETS) +.PHONY: custatevec-tests +custatevec-tests: $(CUSTATEVEC_TARGETS) + .PHONY: run-cxx-tests run-cxx-tests: cxx-tests for exe in $(CXX_TARGETS); do if ! ./$$exe; then exit 1; fi; done @@ -32,6 +38,10 @@ run-cxx-tests: cxx-tests run-cuda-tests: cuda-tests for exe in $(CUDA_TARGETS); do if ! ./$$exe; then exit 1; fi; done +.PHONY: run-custatevec-tests +run-custatevec-tests: custatevec-tests + for exe in $(CUSTATEVEC_TARGETS); do if ! ./$$exe; then exit 1; fi; done + $(GTEST_DIR)/make: -git submodule update --init --recursive googletest mkdir -p $(GTEST_DIR)/make @@ -40,9 +50,12 @@ $(GTEST_DIR)/make: %.x: %.cc $(GTEST_DIR)/make $(CXX) -o ./$@ $< $(TESTFLAGS) $(CXXFLAGS) $(ARCHFLAGS) -%.x: %.cu $(GTEST_DIR)/make +%cuda_test.x: %cuda_test.cu $(GTEST_DIR)/make $(NVCC) -o ./$@ $< $(TESTFLAGS) $(NVCCFLAGS) +%custatevec_test.x: %custatevec_test.cu $(GTEST_DIR)/make + $(NVCC) -o ./$@ $< $(TESTFLAGS) $(NVCCFLAGS) $(CUSTATEVECFLAGS) + .PHONY: clean clean: -rm -f ./*.x ./*.a ./*.so ./*.mod diff --git a/tests/hybrid_custatevec_test.cu b/tests/hybrid_custatevec_test.cu new file mode 100644 index 00000000..82dbeafc --- /dev/null +++ b/tests/hybrid_custatevec_test.cu @@ -0,0 +1,67 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "hybrid_testfixture.h" + +#include +#include + +#include "gtest/gtest.h" + +#include "../lib/simulator_custatevec.h" + +namespace qsim { + +template +struct Factory { + using fp_type = FP; + using Simulator = qsim::SimulatorCuStateVec; + using StateSpace = typename Simulator::StateSpace; + + Factory() { + ErrorCheck(cublasCreate(&cublas_handle)); + ErrorCheck(custatevecCreate(&custatevec_handle)); + } + + ~Factory() { + ErrorCheck(cublasDestroy(cublas_handle)); + ErrorCheck(custatevecDestroy(custatevec_handle)); + } + + StateSpace CreateStateSpace() const { + return StateSpace(cublas_handle, custatevec_handle); + } + + Simulator CreateSimulator() const { + return Simulator(custatevec_handle); + } + + cublasHandle_t cublas_handle; + custatevecHandle_t custatevec_handle; +}; + +TEST(HybridCuStateVecTest, Hybrid2) { + TestHybrid2(qsim::Factory()); +} + +TEST(HybridCuStateVecTest, Hybrid4) { + TestHybrid4(qsim::Factory()); +} + +} // namespace qsim + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/make.sh b/tests/make.sh index 37b91723..10d91b56 100755 --- a/tests/make.sh +++ b/tests/make.sh @@ -19,6 +19,7 @@ path_to_include=googletest/googletest/include path_to_lib=googletest/lib +path_to_lib=googletest/googletest/make/lib g++ -O3 -I$path_to_include -L$path_to_lib -o bitstring_test.x bitstring_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o channels_cirq_test.x channels_cirq_test.cc -lgtest -lpthread @@ -54,3 +55,10 @@ nvcc -O3 -I$path_to_include -L$path_to_lib -o hybrid_cuda_test.x hybrid_cuda_tes nvcc -O3 -I$path_to_include -L$path_to_lib -o qtrajectory_cuda_test.x qtrajectory_cuda_test.cu -lgtest -lpthread nvcc -O3 -I$path_to_include -L$path_to_lib -o simulator_cuda_test.x simulator_cuda_test.cu -lgtest -lpthread nvcc -O3 -I$path_to_include -L$path_to_lib -o statespace_cuda_test.x statespace_cuda_test.cu -lgtest -lpthread + +# CUQUANTUM_ROOT should be set. +CUSTATEVECFLAGS="-I${CUQUANTUM_ROOT}/include -L${CUQUANTUM_ROOT}/lib64 -lcustatevec -lcublas" +nvcc -O3 $CUSTATEVECFLAGS -I$path_to_include -L$path_to_lib -o hybrid_custatevec_test.x hybrid_custatevec_test.cu -lgtest -lpthread +nvcc -O3 $CUSTATEVECFLAGS -I$path_to_include -L$path_to_lib -o qtrajectory_custatevec_test.x qtrajectory_custatevec_test.cu -lgtest -lpthread +nvcc -O3 $CUSTATEVECFLAGS -I$path_to_include -L$path_to_lib -o simulator_custatevec_test.x simulator_custatevec_test.cu -lgtest -lpthread +nvcc -O3 $CUSTATEVECFLAGS -I$path_to_include -L$path_to_lib -o statespace_custatevec_test.x statespace_custatevec_test.cu -lgtest -lpthread diff --git a/tests/qtrajectory_custatevec_test.cu b/tests/qtrajectory_custatevec_test.cu new file mode 100644 index 00000000..c8a2958d --- /dev/null +++ b/tests/qtrajectory_custatevec_test.cu @@ -0,0 +1,83 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "qtrajectory_testfixture.h" + +#include +#include + +#include "gtest/gtest.h" + +#include "../lib/simulator_custatevec.h" + +namespace qsim { + +template +struct Factory { + using fp_type = FP; + using Simulator = qsim::SimulatorCuStateVec; + using StateSpace = typename Simulator::StateSpace; + + Factory() { + ErrorCheck(cublasCreate(&cublas_handle)); + ErrorCheck(custatevecCreate(&custatevec_handle)); + } + + ~Factory() { + ErrorCheck(cublasDestroy(cublas_handle)); + ErrorCheck(custatevecDestroy(custatevec_handle)); + } + + StateSpace CreateStateSpace() const { + return StateSpace(cublas_handle, custatevec_handle); + } + + Simulator CreateSimulator() const { + return Simulator(custatevec_handle); + } + + cublasHandle_t cublas_handle; + custatevecHandle_t custatevec_handle; +}; + +TEST(QTrajectoryCuStateVecTest, BitFlip) { + TestBitFlip(qsim::Factory()); +} + +TEST(QTrajectoryCuStateVecTest, GenDump) { + TestGenDump(qsim::Factory()); +} + +TEST(QTrajectoryCuStateVecTest, CollectKopStat) { + TestCollectKopStat(qsim::Factory()); +} + +TEST(QTrajectoryCuStateVecTest, CleanCircuit) { + TestCleanCircuit(qsim::Factory()); +} + +TEST(QTrajectoryCuStateVecTest, InitialState) { + TestInitialState(qsim::Factory()); +} + +TEST(QTrajectoryCuStateVecTest, UncomputeFinalState) { + TestUncomputeFinalState(qsim::Factory()); +} + +} // namespace qsim + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/simulator_custatevec_test.cu b/tests/simulator_custatevec_test.cu new file mode 100644 index 00000000..646e0c07 --- /dev/null +++ b/tests/simulator_custatevec_test.cu @@ -0,0 +1,108 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "simulator_testfixture.h" + +#include +#include + +#include + +#include "gtest/gtest.h" + +#include "../lib/simulator_custatevec.h" + +namespace qsim { + +template +class SimulatorCuStateVecTest : public testing::Test {}; + +using fp_impl = ::testing::Types; + +TYPED_TEST_SUITE(SimulatorCuStateVecTest, fp_impl); + +template +struct Factory { + using Simulator = qsim::SimulatorCuStateVec; + using StateSpace = typename Simulator::StateSpace; + + Factory() { + ErrorCheck(cublasCreate(&cublas_handle)); + ErrorCheck(custatevecCreate(&custatevec_handle)); + } + + ~Factory() { + ErrorCheck(cublasDestroy(cublas_handle)); + ErrorCheck(custatevecDestroy(custatevec_handle)); + } + + StateSpace CreateStateSpace() const { + return StateSpace(cublas_handle, custatevec_handle); + } + + Simulator CreateSimulator() const { + return Simulator(custatevec_handle); + } + + cublasHandle_t cublas_handle; + custatevecHandle_t custatevec_handle; +}; + +TYPED_TEST(SimulatorCuStateVecTest, ApplyGate1) { + TestApplyGate1(qsim::Factory()); +} + +TYPED_TEST(SimulatorCuStateVecTest, ApplyGate2) { + TestApplyGate2(qsim::Factory()); +} + +TYPED_TEST(SimulatorCuStateVecTest, ApplyGate3) { + TestApplyGate3(qsim::Factory()); +} + +TYPED_TEST(SimulatorCuStateVecTest, ApplyGate5) { + TestApplyGate5(qsim::Factory()); +} + +TYPED_TEST(SimulatorCuStateVecTest, CircuitWithControlledGates) { + TestCircuitWithControlledGates(qsim::Factory()); +} + +TYPED_TEST(SimulatorCuStateVecTest, CircuitWithControlledGatesDagger) { + TestCircuitWithControlledGatesDagger(qsim::Factory()); +} + +TYPED_TEST(SimulatorCuStateVecTest, MultiQubitGates) { + TestMultiQubitGates(qsim::Factory()); +} + +TYPED_TEST(SimulatorCuStateVecTest, ControlledGates) { + bool high_precision = std::is_same::value; + TestControlledGates(qsim::Factory(), high_precision); +} + +TYPED_TEST(SimulatorCuStateVecTest, ExpectationValue1) { + TestExpectationValue1(qsim::Factory()); +} + +TYPED_TEST(SimulatorCuStateVecTest, ExpectationValue2) { + TestExpectationValue2(qsim::Factory()); +} + +} // namespace qsim + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/statespace_custatevec_test.cu b/tests/statespace_custatevec_test.cu new file mode 100644 index 00000000..833bbee0 --- /dev/null +++ b/tests/statespace_custatevec_test.cu @@ -0,0 +1,127 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "statespace_testfixture.h" + +#include +#include + +#include "gtest/gtest.h" + +#include "../lib/simulator_custatevec.h" +#include "../lib/statespace_custatevec.h" + +namespace qsim { + +template +class StateSpaceCuStateVecTest : public testing::Test {}; + +using fp_impl = ::testing::Types; + +TYPED_TEST_SUITE(StateSpaceCuStateVecTest, fp_impl); + +template +struct Factory { + using Simulator = qsim::SimulatorCuStateVec; + using StateSpace = typename Simulator::StateSpace; + + Factory() { + ErrorCheck(cublasCreate(&cublas_handle)); + ErrorCheck(custatevecCreate(&custatevec_handle)); + } + + ~Factory() { + ErrorCheck(cublasDestroy(cublas_handle)); + ErrorCheck(custatevecDestroy(custatevec_handle)); + } + + StateSpace CreateStateSpace() const { + return StateSpace(cublas_handle, custatevec_handle); + } + + Simulator CreateSimulator() const { + return Simulator(custatevec_handle); + } + + cublasHandle_t cublas_handle; + custatevecHandle_t custatevec_handle; +}; + +TYPED_TEST(StateSpaceCuStateVecTest, Add) { + TestAdd(qsim::Factory()); +} + +TYPED_TEST(StateSpaceCuStateVecTest, NormSmall) { + TestNormSmall(qsim::Factory()); +} + +TYPED_TEST(StateSpaceCuStateVecTest, NormAndInnerProductSmall) { + TestNormAndInnerProductSmall(qsim::Factory()); +} + +TYPED_TEST(StateSpaceCuStateVecTest, NormAndInnerProduct) { + TestNormAndInnerProduct(qsim::Factory()); +} + +TYPED_TEST(StateSpaceCuStateVecTest, SamplingSmall) { + TestSamplingSmall(qsim::Factory()); +} + +TYPED_TEST(StateSpaceCuStateVecTest, SamplingCrossEntropyDifference) { + TestSamplingCrossEntropyDifference(qsim::Factory()); +} + +TYPED_TEST(StateSpaceCuStateVecTest, Ordering) { + TestOrdering(qsim::Factory()); +} + +TEST(StateSpaceCuStateVecTest, MeasurementSmall) { + TestMeasurementSmall(qsim::Factory(), true); +} + +TYPED_TEST(StateSpaceCuStateVecTest, MeasurementLarge) { +// This test fails. +// TestMeasurementLarge(qsim::Factory()); +} + +TYPED_TEST(StateSpaceCuStateVecTest, Collapse) { +// This test fails. + TestCollapse(qsim::Factory()); +} + +TEST(StateSpaceCuStateVecTest, InvalidStateSize) { + TestInvalidStateSize(qsim::Factory()); +} + +TYPED_TEST(StateSpaceCuStateVecTest, BulkSetAmpl) { +// Not implemented. +// TestBulkSetAmplitude(qsim::Factory()); +} + +TYPED_TEST(StateSpaceCuStateVecTest, BulkSetAmplExclusion) { +// Not implemented. +// TestBulkSetAmplitudeExclusion(qsim::Factory()); +} + +TYPED_TEST(StateSpaceCuStateVecTest, BulkSetAmplDefault) { +// Not implemented. +// TestBulkSetAmplitudeDefault(factory); +} + +} // namespace qsim + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From 4c18f7fc0fd7e140ff93ab275d2175d099da12c2 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Mon, 8 Nov 2021 17:33:48 +0100 Subject: [PATCH 145/246] Fix Python format. --- qsimcirq/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qsimcirq/__init__.py b/qsimcirq/__init__.py index 8b7db6ba..89c6197e 100644 --- a/qsimcirq/__init__.py +++ b/qsimcirq/__init__.py @@ -38,6 +38,7 @@ def _load_qsim_gpu(): qsim_gpu = None return qsim_gpu + def _load_qsim_custatevec(): instr = qsim_decide.detect_custatevec() if instr == 1: @@ -46,6 +47,7 @@ def _load_qsim_custatevec(): qsim_custatevec = None return qsim_custatevec + qsim = _load_simd_qsim() qsim_gpu = _load_qsim_gpu() qsim_custatevec = _load_qsim_custatevec() From 8a48dc3f4a899a502afa7934cdbd1f87238604bb Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 8 Nov 2021 08:56:19 -0800 Subject: [PATCH 146/246] Link to cuQuantum SDK --- docs/tutorials/gcp_gpu.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index 234d6dd6..f064ff26 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -177,8 +177,8 @@ After a moment, you should see a result that looks similar to the following. ### Optional: Use the NVIDIA cuQuantum SDK -If you have the NVIDIA cuQuantum SDK installed, you can use it here by -modifying the `gpu_options` line like so: +If you have the [NVIDIA cuQuantum SDK](https://developer.nvidia.com/cuquantum-sdk) +installed, you can use it here by modifying the `gpu_options` line like so: ```python gpu_options = qsimcirq.QSimOptions(use_gpu=True, gpu_mode=1) From a8a4040e7a52d79f4b87b83691b90cc6cadfe0ab Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 8 Nov 2021 08:57:55 -0800 Subject: [PATCH 147/246] Update version to 0.11.0 --- qsimcirq/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq/_version.py b/qsimcirq/_version.py index 0d4f6478..28e37090 100644 --- a/qsimcirq/_version.py +++ b/qsimcirq/_version.py @@ -1,3 +1,3 @@ """The version number defined here is read automatically in setup.py.""" -__version__ = "0.10.3" +__version__ = "0.11.0" From 823f7c00301fbd96f5eac6b8de560865919b3409 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 8 Nov 2021 09:30:31 -0800 Subject: [PATCH 148/246] Note about CUQUANTUM_ROOT --- docs/tutorials/gcp_gpu.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index f064ff26..5235b418 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -178,7 +178,14 @@ After a moment, you should see a result that looks similar to the following. ### Optional: Use the NVIDIA cuQuantum SDK If you have the [NVIDIA cuQuantum SDK](https://developer.nvidia.com/cuquantum-sdk) -installed, you can use it here by modifying the `gpu_options` line like so: +installed, you can use it with this tutorial. Before building qsim in step 5, +set the `CUQUANTUM_ROOT` environment variable from the command line: + +```bash +CUQUANTUM_ROOT=[PATH_TO_CUQUANTUM_SDK] +``` + +Once you have built qsim, modify the `gpu_options` line like so: ```python gpu_options = qsimcirq.QSimOptions(use_gpu=True, gpu_mode=1) From 19137761b68507e1310ac402f90ed87054ca7d0a Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 8 Nov 2021 10:05:25 -0800 Subject: [PATCH 149/246] export CUQUANTUM_ROOT --- docs/tutorials/gcp_gpu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index 5235b418..e437f3e2 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -182,7 +182,7 @@ installed, you can use it with this tutorial. Before building qsim in step 5, set the `CUQUANTUM_ROOT` environment variable from the command line: ```bash -CUQUANTUM_ROOT=[PATH_TO_CUQUANTUM_SDK] +export CUQUANTUM_ROOT=[PATH_TO_CUQUANTUM_SDK] ``` Once you have built qsim, modify the `gpu_options` line like so: From 05761621217604abb9f050485ee78a1a40b854ef Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 8 Nov 2021 15:32:28 -0800 Subject: [PATCH 150/246] Revert "refactor: split dev requirements out of main requirements.txt" --- .github/workflows/python_format.yml | 6 ++++-- .github/workflows/testing_wheels.yml | 1 - MANIFEST.in | 1 - dev-requirements.txt | 3 --- docs/install_qsimcirq.md | 6 ------ requirements.txt | 12 ++++++++++-- setup.py | 4 ---- 7 files changed, 14 insertions(+), 19 deletions(-) delete mode 100644 dev-requirements.txt diff --git a/.github/workflows/python_format.yml b/.github/workflows/python_format.yml index 213abf2e..adb5f56f 100644 --- a/.github/workflows/python_format.yml +++ b/.github/workflows/python_format.yml @@ -24,7 +24,9 @@ jobs: with: python-version: '3.7' architecture: 'x64' - - name: Install dev requirements - run: pip install -r dev-requirements.txt + - name: Install flynt + run: cat requirements.txt | grep flynt | xargs pip install + - name: Install black + run: cat requirements.txt | grep black | xargs pip install - name: Format run: check/format-incremental diff --git a/.github/workflows/testing_wheels.yml b/.github/workflows/testing_wheels.yml index 55dd237a..eeb4d741 100644 --- a/.github/workflows/testing_wheels.yml +++ b/.github/workflows/testing_wheels.yml @@ -45,7 +45,6 @@ jobs: CIBW_REPAIR_WHEEL_COMMAND_MACOS: "" # due to package and module name conflict have to temporarily move it away to run tests CIBW_BEFORE_TEST: "mv {package}/qsimcirq /tmp" - CIBW_TEST_EXTRAS: "dev" CIBW_TEST_COMMAND: "pytest {package}/qsimcirq_tests/qsimcirq_test.py && mv /tmp/qsimcirq {package}" steps: - uses: actions/checkout@v2 diff --git a/MANIFEST.in b/MANIFEST.in index 4b487267..2968589b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,4 @@ include requirements.txt -include dev-requirements.txt include CMakeLists.txt graft pybind_interface diff --git a/dev-requirements.txt b/dev-requirements.txt deleted file mode 100644 index 4cb3afc0..00000000 --- a/dev-requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -black==20.8b1 -flynt~=0.60 -pytest diff --git a/docs/install_qsimcirq.md b/docs/install_qsimcirq.md index 0a0f3182..9ae35d21 100644 --- a/docs/install_qsimcirq.md +++ b/docs/install_qsimcirq.md @@ -17,12 +17,6 @@ Prerequisites are included in the [`requirements.txt`](https://github.com/quantumlib/qsim/blob/master/requirements.txt) file, and will be automatically installed along with qsimcirq. -If you'd like to develop qsimcirq, a separate set of dependencies are includes -in the -[`dev-requirements.txt`](https://github.com/quantumlib/qsim/blob/master/dev-requirements.txt) -file. You can install them with `pip3 install -r dev-requirements.txt` or -`pip3 install qsimcirq[dev]`. - ## Linux installation We provide `qsimcirq` Python wheels on 64-bit `x86` architectures with `Python 3.{6,7,8,9}`. diff --git a/requirements.txt b/requirements.txt index 0a95d64c..01d20cd5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,13 @@ -absl-py +# Runtime requirements for the python 3 version of cirq. + cirq-core numpy~=1.16 -pybind11 typing_extensions +absl-py + +# Build and test requirements + +black==20.8b1 +flynt~=0.60 +pybind11 +pytest diff --git a/setup.py b/setup.py index e1e80a70..2fc70507 100644 --- a/setup.py +++ b/setup.py @@ -78,7 +78,6 @@ def build_extension(self, ext): requirements = open("requirements.txt").readlines() -dev_requirements = open("dev-requirements.txt").readlines() description = "Schrödinger and Schrödinger-Feynman simulators for quantum circuits." @@ -95,9 +94,6 @@ def build_extension(self, ext): author_email="devabathini92@gmail.com", python_requires=">=3.3.0", install_requires=requirements, - extras_require={ - "dev": dev_requirements, - }, license="Apache 2", description=description, long_description=long_description, From 51ba016984d63f2594a57bbbc1b368f28e746f8d Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 8 Nov 2021 15:36:00 -0800 Subject: [PATCH 151/246] Revert #469 --- .github/workflows/cirq_compatibility.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/cirq_compatibility.yml b/.github/workflows/cirq_compatibility.yml index f4fd7473..04e13559 100644 --- a/.github/workflows/cirq_compatibility.yml +++ b/.github/workflows/cirq_compatibility.yml @@ -18,7 +18,5 @@ jobs: run: pip3 install -U cirq --pre - name: Install qsim requirements run: pip3 install -r requirements.txt - - name: Install test requirements - run: pip3 install -r dev-requirements.txt - name: Run python tests run: make run-py-tests From f59589f5f13813041ae0b0c88596a206ddd43413 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 8 Nov 2021 15:41:24 -0800 Subject: [PATCH 152/246] Update version to 0.11.1 --- qsimcirq/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq/_version.py b/qsimcirq/_version.py index 28e37090..bf85e4ce 100644 --- a/qsimcirq/_version.py +++ b/qsimcirq/_version.py @@ -1,3 +1,3 @@ """The version number defined here is read automatically in setup.py.""" -__version__ = "0.11.0" +__version__ = "0.11.1" From 11f8b121d0b8c13a6e0839a674150d16006b9b92 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Tue, 9 Nov 2021 16:48:47 +0100 Subject: [PATCH 153/246] Change environment variable name. --- CMakeLists.txt | 2 +- Makefile | 4 ++-- apps/make.sh | 4 ++-- docs/cirq_interface.md | 2 +- docs/tutorials/gcp_gpu.md | 6 +++--- pybind_interface/Makefile | 2 +- pybind_interface/custatevec/CMakeLists.txt | 4 ++-- pybind_interface/decide/CMakeLists.txt | 2 +- tests/make.sh | 4 ++-- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 615a33db..fba9db45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if(has_nvcc STREQUAL "") else() project(qsim LANGUAGES CXX CUDA) ADD_SUBDIRECTORY(pybind_interface/cuda) - if(DEFINED ENV{CUQUANTUM_ROOT}) + if(DEFINED ENV{CUQUANTUM_DIR}) ADD_SUBDIRECTORY(pybind_interface/custatevec) endif() endif() diff --git a/Makefile b/Makefile index 900aa3cd..706ffab6 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,8 @@ CXXFLAGS = -O3 -fopenmp ARCHFLAGS = -march=native NVCCFLAGS = -O3 -# CUQUANTUM_ROOT should be set. -CUSTATEVECFLAGS = -I$(CUQUANTUM_ROOT)/include -L$(CUQUANTUM_ROOT)/lib64 -lcustatevec -lcublas +# CUQUANTUM_DIR should be set. +CUSTATEVECFLAGS = -I$(CUQUANTUM_DIR)/include -L$(CUQUANTUM_DIR)/lib64 -lcustatevec -lcublas PYBIND11 = true diff --git a/apps/make.sh b/apps/make.sh index 51052cc5..7b2db2c8 100755 --- a/apps/make.sh +++ b/apps/make.sh @@ -26,6 +26,6 @@ g++ -O3 -march=native -fopenmp -o qsimh_amplitudes.x qsimh_amplitudes.cc nvcc -O3 -o qsim_base_cuda.x qsim_base_cuda.cu nvcc -O3 -o qsim_qtrajectory_cuda.x qsim_qtrajectory_cuda.cu -# CUQUANTUM_ROOT should be set. -CUSTATEVECFLAGS="-I${CUQUANTUM_ROOT}/include -L${CUQUANTUM_ROOT}/lib64 -lcustatevec -lcublas" +# CUQUANTUM_DIR should be set. +CUSTATEVECFLAGS="-I${CUQUANTUM_DIR}/include -L${CUQUANTUM_DIR}/lib64 -lcustatevec -lcublas" nvcc -O3 $CUSTATEVECFLAGS -o qsim_base_custatevec.x qsim_base_custatevec.cu diff --git a/docs/cirq_interface.md b/docs/cirq_interface.md index 8f610450..321c985c 100644 --- a/docs/cirq_interface.md +++ b/docs/cirq_interface.md @@ -180,7 +180,7 @@ and run on a device with available NVIDIA GPUs. Compilation for GPU follows the same steps outlined in the [Compiling qsimcirq](./cirq_interface.md#compiling-qsimcirq) section. To compile with the NVIDIA cuStateVec library, set the environmment variable -`CUQUANTUM_ROOT` to path to the root of the cuStateVec library. +`CUQUANTUM_DIR` to the path to the cuStateVec library. `QSimOptions` provides five parameters to configure GPU execution. `use_gpu` is required to enable GPU execution: diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index e437f3e2..f18ae61f 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -175,14 +175,14 @@ After a moment, you should see a result that looks similar to the following. [(0.7071067690849304+0j), 0j] ``` -### Optional: Use the NVIDIA cuQuantum SDK +### Optional: Use the NVIDIA cuQuantum SDK If you have the [NVIDIA cuQuantum SDK](https://developer.nvidia.com/cuquantum-sdk) installed, you can use it with this tutorial. Before building qsim in step 5, -set the `CUQUANTUM_ROOT` environment variable from the command line: +set the `CUQUANTUM_DIR` environment variable from the command line: ```bash -export CUQUANTUM_ROOT=[PATH_TO_CUQUANTUM_SDK] +export CUQUANTUM_DIR=[PATH_TO_CUQUANTUM_SDK] ``` Once you have built qsim, modify the `gpu_options` line like so: diff --git a/pybind_interface/Makefile b/pybind_interface/Makefile index c8944562..6a7dc7eb 100644 --- a/pybind_interface/Makefile +++ b/pybind_interface/Makefile @@ -24,7 +24,7 @@ ifeq ($(shell which $(NVCC)),) pybind: pybind-cpu decide-cpu else # Check for the cuStateVec library. -ifeq ($(CUQUANTUM_ROOT),) +ifeq ($(CUQUANTUM_DIR),) pybind: pybind-cpu pybind-gpu decide-gpu else pybind: pybind-cpu pybind-gpu pybind-custatevec decide-custatevec diff --git a/pybind_interface/custatevec/CMakeLists.txt b/pybind_interface/custatevec/CMakeLists.txt index 4ea57380..0d9a05fb 100644 --- a/pybind_interface/custatevec/CMakeLists.txt +++ b/pybind_interface/custatevec/CMakeLists.txt @@ -45,8 +45,8 @@ find_package(CUDA REQUIRED) include_directories(${PYTHON_INCLUDE_DIRS} ${pybind11_SOURCE_DIR}/include) -include_directories($ENV{CUQUANTUM_ROOT}/include) -link_directories($ENV{CUQUANTUM_ROOT}/lib64) +include_directories($ENV{CUQUANTUM_DIR}/include) +link_directories($ENV{CUQUANTUM_DIR}/lib64) cuda_add_library(qsim_custatevec MODULE pybind_main_custatevec.cpp) target_link_libraries(qsim_custatevec -lcustatevec -lcublas) diff --git a/pybind_interface/decide/CMakeLists.txt b/pybind_interface/decide/CMakeLists.txt index 99a75881..643ca9ad 100644 --- a/pybind_interface/decide/CMakeLists.txt +++ b/pybind_interface/decide/CMakeLists.txt @@ -42,7 +42,7 @@ else() cuda_add_library(qsim_decide MODULE decide.cpp) - if(DEFINED ENV{CUQUANTUM_ROOT}) + if(DEFINED ENV{CUQUANTUM_DIR}) target_compile_options(qsim_decide PRIVATE $<$:-D__CUSTATEVEC__> ) diff --git a/tests/make.sh b/tests/make.sh index 10d91b56..5df5a37e 100755 --- a/tests/make.sh +++ b/tests/make.sh @@ -56,8 +56,8 @@ nvcc -O3 -I$path_to_include -L$path_to_lib -o qtrajectory_cuda_test.x qtrajector nvcc -O3 -I$path_to_include -L$path_to_lib -o simulator_cuda_test.x simulator_cuda_test.cu -lgtest -lpthread nvcc -O3 -I$path_to_include -L$path_to_lib -o statespace_cuda_test.x statespace_cuda_test.cu -lgtest -lpthread -# CUQUANTUM_ROOT should be set. -CUSTATEVECFLAGS="-I${CUQUANTUM_ROOT}/include -L${CUQUANTUM_ROOT}/lib64 -lcustatevec -lcublas" +# CUQUANTUM_DIR should be set. +CUSTATEVECFLAGS="-I${CUQUANTUM_DIR}/include -L${CUQUANTUM_DIR}/lib64 -lcustatevec -lcublas" nvcc -O3 $CUSTATEVECFLAGS -I$path_to_include -L$path_to_lib -o hybrid_custatevec_test.x hybrid_custatevec_test.cu -lgtest -lpthread nvcc -O3 $CUSTATEVECFLAGS -I$path_to_include -L$path_to_lib -o qtrajectory_custatevec_test.x qtrajectory_custatevec_test.cu -lgtest -lpthread nvcc -O3 $CUSTATEVECFLAGS -I$path_to_include -L$path_to_lib -o simulator_custatevec_test.x simulator_custatevec_test.cu -lgtest -lpthread From 60774853ac65fb80c4b360bc02f74609de7d6f26 Mon Sep 17 00:00:00 2001 From: jlow2397 <54644848+jlow2397@users.noreply.github.com> Date: Wed, 10 Nov 2021 14:07:48 -0800 Subject: [PATCH 154/246] updating cards on qsim documentation landing page --- docs/_index.yaml | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/docs/_index.yaml b/docs/_index.yaml index 093ca58b..41c71faf 100644 --- a/docs/_index.yaml +++ b/docs/_index.yaml @@ -78,28 +78,11 @@ landing_page: options: - cards items: - - heading: "Schrödinger simulation via qsim" - image_path: /site-assets/images/cards/qsim-card-schrodinger.png - description: > - qsim is a full wavefunction simulator that has been optimized to support - vectorized operations and multi-threading. - buttons: - - label: "Learn more" - path: /qsim/usage - - heading: "Schrödinger-Feynman simulation via qsimh" - image_path: /site-assets/images/cards/qsim-card-schrodinger-feynman.png - description: > - qsimh is a hybrid Schrödinger-Feynman simulator. It simulates separate - disjoint sets of qubit using a full wave vector simulator, and then uses - Feynman paths to sum over gates that span the sets. - buttons: - - label: "Learn more" - path: /qsim/usage#qsimh_base_usage - heading: "Cirq integration" image_path: /site-assets/images/cards/qsim-card-cirq-integrations.png description: > Cirq is a python framework for writing, simulating, and executing - quantum programs. Cirq’s built in simulator is useful to around 20 + quantum programs. Cirq's built in simulator is useful to around 20 qubits. By using the qsim Cirq simulator one can boost the number of qubits simulated to be mostly limited by available ram. Up to 40 qubits can be simulated on a 90 core Intel Xeon workstation. @@ -109,9 +92,27 @@ landing_page: - heading: "Install qsim on GCP" image_path: /site-assets/images/cards/qsim-card-gcp.jpg description: > - Learn how to simulate up to 38 qubits on Google Cloud’s Compute Engine. + Learn how to simulate up to 38 qubits on Google Cloud's Compute Engine. qsim has a prepackaged docker image that allows easy deployment of qsim, Juypter, and Cirq onto a virtual machine. buttons: - label: "Learn more" - path: /qsim/tutorials/qsimcirq_gcp + path: /qsim/tutorials/gcp_before_you_begin + - heading: "Upgrades to qsim" + image_path: /site-assets/images/cards/qsim-card-schrodinger.png + description: > + To help researchers and developers develop quantum algorithms today, we + have made updates to qsim that make it more performant and intuitive, + and more “hardware-like”. + buttons: + - label: "Learn more" + path: https://opensource.googleblog.com/2021/11/Upgrading%20qsim%20Google%20Quantum%20AIs%20Open%20Source%20Quantum%20Simulator%20.html?linkId=138925083 + - heading: "Integrating qsim with NVIDIA's cuQuantum SDK" + image_path: /qsim/images/qsim_nvidia.png + description: > + The integration between qsim and the NVIDIA cuQuantum SDK will enable + qsim users to make the most of GPUs when developing quantum algorithms + and applications. + buttons: + - label: "Learn more" + path: https://opensource.googleblog.com/2021/11/qsim%20integrates%20with%20NVIDIA%20cuQuantum%20SDK%20to%20accelerate%20quantum%20circuit%20simulations%20on%20NVIDIA%20GPUs.html From 8570fe5f5a9ac6fc1f82759c7d3ee8922a1a65c3 Mon Sep 17 00:00:00 2001 From: jlow2397 <54644848+jlow2397@users.noreply.github.com> Date: Thu, 11 Nov 2021 14:06:03 -0800 Subject: [PATCH 155/246] removing curly quotes from documentation on qsim --- docs/_index.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_index.yaml b/docs/_index.yaml index 41c71faf..e525f69c 100644 --- a/docs/_index.yaml +++ b/docs/_index.yaml @@ -103,7 +103,7 @@ landing_page: description: > To help researchers and developers develop quantum algorithms today, we have made updates to qsim that make it more performant and intuitive, - and more “hardware-like”. + and more "hardware-like". buttons: - label: "Learn more" path: https://opensource.googleblog.com/2021/11/Upgrading%20qsim%20Google%20Quantum%20AIs%20Open%20Source%20Quantum%20Simulator%20.html?linkId=138925083 From f0175cafa9e2b59ed5796d4b8876be4ba1c5e457 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 16 Nov 2021 08:49:31 -0800 Subject: [PATCH 156/246] Reinstate `noise` parameter --- qsimcirq/qsim_simulator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index b37b646b..cd199213 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -191,6 +191,7 @@ def __init__( self, qsim_options: Union[None, Dict, QSimOptions] = None, seed: value.RANDOM_STATE_OR_SEED_LIKE = None, + noise: "devices.NOISE_MODEL_LIKE" = None, circuit_memoization_size: int = 0, ): """Creates a new QSimSimulator using the given options and seed. @@ -200,6 +201,8 @@ def __init__( to use for all circuits run using this simulator. See the QSimOptions class for details. seed: A random state or seed object, as defined in cirq.value. + noise: A cirq.NoiseModel to apply to all circuits simulated with + this simulator. circuit_memoization_size: The number of last translated circuits to be memoized from simulation executions, to eliminate translation overhead. Every simulation will perform a linear From f91d8bab6d599c39d355f43c0b5b4fe9a695e4ae Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 16 Nov 2021 10:48:30 -0800 Subject: [PATCH 157/246] Skip applying noise if none is provided, part 1 --- qsimcirq/qsim_simulator.py | 54 ++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index cd199213..b273b398 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -309,13 +309,15 @@ def _sample_measure_results( """ # Add noise to the circuit if a noise model was provided. - program = qsimc.QSimCircuit( - self.noise.noisy_moments(program, sorted(program.all_qubits())), - device=program.device, - ) + all_qubits = program.all_qubits() + if self.noise is not devices.NO_NOISE: + program = qsimc.QSimCircuit( + self.noise.noisy_moments(program, sorted(all_qubits)), + device=program.device, + ) # Compute indices of measured qubits - ordered_qubits = ops.QubitOrder.DEFAULT.order_for(program.all_qubits()) + ordered_qubits = ops.QubitOrder.DEFAULT.order_for(all_qubits) num_qubits = len(ordered_qubits) qubit_map = {qubit: index for index, qubit in enumerate(ordered_qubits)} @@ -434,15 +436,15 @@ def compute_amplitudes_sweep( """ # Add noise to the circuit if a noise model was provided. - program = qsimc.QSimCircuit( - self.noise.noisy_moments(program, sorted(program.all_qubits())), - device=program.device, - ) + all_qubits = program.all_qubits() + if self.noise is not devices.NO_NOISE: + program = qsimc.QSimCircuit( + self.noise.noisy_moments(program, sorted(all_qubits)), + device=program.device, + ) # qsim numbers qubits in reverse order from cirq - cirq_order = ops.QubitOrder.as_qubit_order(qubit_order).order_for( - program.all_qubits() - ) + cirq_order = ops.QubitOrder.as_qubit_order(qubit_order).order_for(all_qubits) num_qubits = len(cirq_order) bitstrings = [ format(bitstring, "b").zfill(num_qubits)[::-1] for bitstring in bitstrings @@ -516,19 +518,19 @@ def simulate_sweep( raise TypeError("initial_state must be an int or state vector.") # Add noise to the circuit if a noise model was provided. - program = qsimc.QSimCircuit( - self.noise.noisy_moments(program, sorted(program.all_qubits())), - device=program.device, - ) + all_qubits = program.all_qubits() + if self.noise is not devices.NO_NOISE: + program = qsimc.QSimCircuit( + self.noise.noisy_moments(program, sorted(all_qubits)), + device=program.device, + ) options = {} options.update(self.qsim_options) param_resolvers = study.to_resolvers(params) # qsim numbers qubits in reverse order from cirq - cirq_order = ops.QubitOrder.as_qubit_order(qubit_order).order_for( - program.all_qubits() - ) + cirq_order = ops.QubitOrder.as_qubit_order(qubit_order).order_for(all_qubits) qsim_order = list(reversed(cirq_order)) num_qubits = len(qsim_order) if isinstance(initial_state, np.ndarray): @@ -626,9 +628,8 @@ def simulate_expectation_values_sweep( observables = [observables] psumlist = [ops.PauliSum.wrap(pslike) for pslike in observables] - cirq_order = ops.QubitOrder.as_qubit_order(qubit_order).order_for( - program.all_qubits() - ) + all_qubits = program.all_qubits() + cirq_order = ops.QubitOrder.as_qubit_order(qubit_order).order_for(all_qubits) qsim_order = list(reversed(cirq_order)) num_qubits = len(qsim_order) qubit_map = {qubit: index for index, qubit in enumerate(qsim_order)} @@ -653,10 +654,11 @@ def simulate_expectation_values_sweep( raise TypeError("initial_state must be an int or state vector.") # Add noise to the circuit if a noise model was provided. - program = qsimc.QSimCircuit( - self.noise.noisy_moments(program, sorted(program.all_qubits())), - device=program.device, - ) + if self.noise is not devices.NO_NOISE: + program = qsimc.QSimCircuit( + self.noise.noisy_moments(program, sorted(all_qubits)), + device=program.device, + ) options = {} options.update(self.qsim_options) From 6d15523bb12e07b54a7ff3a5824005c95d0c899f Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 16 Nov 2021 10:53:52 -0800 Subject: [PATCH 158/246] Skip applying noise if none is provided, part 2 --- qsimcirq/qsim_simulator.py | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index b273b398..364ebae4 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -310,11 +310,11 @@ def _sample_measure_results( # Add noise to the circuit if a noise model was provided. all_qubits = program.all_qubits() - if self.noise is not devices.NO_NOISE: - program = qsimc.QSimCircuit( - self.noise.noisy_moments(program, sorted(all_qubits)), - device=program.device, - ) + program = qsimc.QSimCircuit( + self.noise.noisy_moments(program, sorted(all_qubits)) + if self.noise is not devices.NO_NOISE else program, + device=program.device, + ) # Compute indices of measured qubits ordered_qubits = ops.QubitOrder.DEFAULT.order_for(all_qubits) @@ -437,11 +437,11 @@ def compute_amplitudes_sweep( # Add noise to the circuit if a noise model was provided. all_qubits = program.all_qubits() - if self.noise is not devices.NO_NOISE: - program = qsimc.QSimCircuit( - self.noise.noisy_moments(program, sorted(all_qubits)), - device=program.device, - ) + program = qsimc.QSimCircuit( + self.noise.noisy_moments(program, sorted(all_qubits)) + if self.noise is not devices.NO_NOISE else program, + device=program.device, + ) # qsim numbers qubits in reverse order from cirq cirq_order = ops.QubitOrder.as_qubit_order(qubit_order).order_for(all_qubits) @@ -519,11 +519,11 @@ def simulate_sweep( # Add noise to the circuit if a noise model was provided. all_qubits = program.all_qubits() - if self.noise is not devices.NO_NOISE: - program = qsimc.QSimCircuit( - self.noise.noisy_moments(program, sorted(all_qubits)), - device=program.device, - ) + program = qsimc.QSimCircuit( + self.noise.noisy_moments(program, sorted(all_qubits)) + if self.noise is not devices.NO_NOISE else program, + device=program.device, + ) options = {} options.update(self.qsim_options) @@ -654,11 +654,11 @@ def simulate_expectation_values_sweep( raise TypeError("initial_state must be an int or state vector.") # Add noise to the circuit if a noise model was provided. - if self.noise is not devices.NO_NOISE: - program = qsimc.QSimCircuit( - self.noise.noisy_moments(program, sorted(all_qubits)), - device=program.device, - ) + program = qsimc.QSimCircuit( + self.noise.noisy_moments(program, sorted(all_qubits)) + if self.noise is not devices.NO_NOISE else program, + device=program.device, + ) options = {} options.update(self.qsim_options) From 44dc3be3ffb9f6da037de3dc7b910b49b02ef52c Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 16 Nov 2021 11:04:41 -0800 Subject: [PATCH 159/246] Formatting fixes --- qsimcirq/qsim_simulator.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 364ebae4..1ed05505 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -312,7 +312,8 @@ def _sample_measure_results( all_qubits = program.all_qubits() program = qsimc.QSimCircuit( self.noise.noisy_moments(program, sorted(all_qubits)) - if self.noise is not devices.NO_NOISE else program, + if self.noise is not devices.NO_NOISE + else program, device=program.device, ) @@ -439,7 +440,8 @@ def compute_amplitudes_sweep( all_qubits = program.all_qubits() program = qsimc.QSimCircuit( self.noise.noisy_moments(program, sorted(all_qubits)) - if self.noise is not devices.NO_NOISE else program, + if self.noise is not devices.NO_NOISE + else program, device=program.device, ) @@ -521,7 +523,8 @@ def simulate_sweep( all_qubits = program.all_qubits() program = qsimc.QSimCircuit( self.noise.noisy_moments(program, sorted(all_qubits)) - if self.noise is not devices.NO_NOISE else program, + if self.noise is not devices.NO_NOISE + else program, device=program.device, ) @@ -656,7 +659,8 @@ def simulate_expectation_values_sweep( # Add noise to the circuit if a noise model was provided. program = qsimc.QSimCircuit( self.noise.noisy_moments(program, sorted(all_qubits)) - if self.noise is not devices.NO_NOISE else program, + if self.noise is not devices.NO_NOISE + else program, device=program.device, ) From bf7b347f81639c5ce343c8b9b35bd9cf96620fc9 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 19 Nov 2021 08:33:36 -0800 Subject: [PATCH 160/246] Link to cuQuantum install guide --- docs/tutorials/gcp_gpu.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index f18ae61f..e352cfe9 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -178,7 +178,9 @@ After a moment, you should see a result that looks similar to the following. ### Optional: Use the NVIDIA cuQuantum SDK If you have the [NVIDIA cuQuantum SDK](https://developer.nvidia.com/cuquantum-sdk) -installed, you can use it with this tutorial. Before building qsim in step 5, +installed (instructions are provided +[here](https://docs.nvidia.com/cuda/cuquantum/custatevec/html/getting_started.html#installation-and-compilation)), +you can use it with this tutorial. Before building qsim in step 5, set the `CUQUANTUM_DIR` environment variable from the command line: ```bash From 50cfc8aeb63457184681737736adb677f2a5fc33 Mon Sep 17 00:00:00 2001 From: Matthew Neeley Date: Fri, 19 Nov 2021 11:54:27 -0800 Subject: [PATCH 161/246] Import and use cirq instead of submodules --- qsimcirq/qsim_circuit.py | 7 +- qsimcirq/qsim_simulator.py | 127 ++++++++++++++++-------------------- qsimcirq/qsimh_simulator.py | 16 ++--- 3 files changed, 70 insertions(+), 80 deletions(-) diff --git a/qsimcirq/qsim_circuit.py b/qsimcirq/qsim_circuit.py index a3089752..a4811e2b 100644 --- a/qsimcirq/qsim_circuit.py +++ b/qsimcirq/qsim_circuit.py @@ -12,12 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import numpy as np import warnings +from typing import Dict, Union import cirq +import numpy as np + from . import qsim -from typing import Dict, Union # List of parameter names that appear in valid Cirq protos. @@ -300,7 +301,7 @@ def has_qsim_kind(op: cirq.ops.GateOperation): return _cirq_gate_kind(op.gate) != None def to_matrix(op: cirq.ops.GateOperation): - mat = cirq.protocols.unitary(op.gate, None) + mat = cirq.unitary(op.gate, None) if mat is None: return NotImplemented diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 1ed05505..a24ffd89 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -16,18 +16,7 @@ from dataclasses import dataclass from typing import Any, Dict, List, Optional, Sequence, Tuple, Union -from cirq import ( - circuits, - devices, - ops, - protocols, - sim, - study, - value, - SimulatesAmplitudes, - SimulatesFinalState, - SimulatesSamples, -) +import cirq # TODO: import from cirq directly when fix is released from cirq.sim.simulator import SimulatesExpectationValues @@ -38,17 +27,17 @@ import qsimcirq.qsim_circuit as qsimc -class QSimSimulatorState(sim.StateVectorSimulatorState): - def __init__(self, qsim_data: np.ndarray, qubit_map: Dict[ops.Qid, int]): +class QSimSimulatorState(cirq.StateVectorSimulatorState): + def __init__(self, qsim_data: np.ndarray, qubit_map: Dict[cirq.Qid, int]): state_vector = qsim_data.view(np.complex64) super().__init__(state_vector=state_vector, qubit_map=qubit_map) -@value.value_equality(unhashable=True) -class QSimSimulatorTrialResult(sim.StateVectorMixin, sim.SimulationTrialResult): +@cirq.value_equality(unhashable=True) +class QSimSimulatorTrialResult(cirq.StateVectorMixin, cirq.SimulationTrialResult): def __init__( self, - params: study.ParamResolver, + params: cirq.ParamResolver, measurements: Dict[str, np.ndarray], final_simulator_state: QSimSimulatorState, ): @@ -102,17 +91,17 @@ def __repr__(self) -> str: # This should probably live in Cirq... # TODO: update to support CircuitOperations. -def _needs_trajectories(circuit: circuits.Circuit) -> bool: +def _needs_trajectories(circuit: cirq.Circuit) -> bool: """Checks if the circuit requires trajectory simulation.""" for op in circuit.all_operations(): test_op = ( op - if not protocols.is_parameterized(op) - else protocols.resolve_parameters( - op, {param: 1 for param in protocols.parameter_names(op)} + if not cirq.is_parameterized(op) + else cirq.resolve_parameters( + op, {param: 1 for param in cirq.parameter_names(op)} ) ) - if not (protocols.has_unitary(test_op) or protocols.is_measurement(test_op)): + if not (cirq.has_unitary(test_op) or cirq.is_measurement(test_op)): return True return False @@ -182,16 +171,16 @@ def as_dict(self): class QSimSimulator( - SimulatesSamples, - SimulatesAmplitudes, - SimulatesFinalState, + cirq.SimulatesSamples, + cirq.SimulatesAmplitudes, + cirq.SimulatesFinalState, SimulatesExpectationValues, ): def __init__( self, qsim_options: Union[None, Dict, QSimOptions] = None, - seed: value.RANDOM_STATE_OR_SEED_LIKE = None, - noise: "devices.NOISE_MODEL_LIKE" = None, + seed: cirq.RANDOM_STATE_OR_SEED_LIKE = None, + noise: cirq.NOISE_MODEL_LIKE = None, circuit_memoization_size: int = 0, ): """Creates a new QSimSimulator using the given options and seed. @@ -225,10 +214,10 @@ def __init__( 'Keys {"c", "i", "s"} are reserved for internal use and cannot be ' "used in QSimCircuit instantiation." ) - self._prng = value.parse_random_state(seed) + self._prng = cirq.parse_random_state(seed) self.qsim_options = QSimOptions().as_dict() self.qsim_options.update(qsim_options) - self.noise = devices.NoiseModel.from_noise_model_like(noise) + self.noise = cirq.NoiseModel.from_noise_model_like(noise) # module to use for simulation if self.qsim_options["g"]: @@ -263,8 +252,8 @@ def get_seed(self): def _run( self, - circuit: circuits.Circuit, - param_resolver: study.ParamResolver, + circuit: cirq.Circuit, + param_resolver: cirq.ParamResolver, repetitions: int, ) -> Dict[str, np.ndarray]: """Run a simulation, mimicking quantum hardware. @@ -278,14 +267,14 @@ def _run( A dictionary from measurement gate key to measurement results. """ - param_resolver = param_resolver or study.ParamResolver({}) - solved_circuit = protocols.resolve_parameters(circuit, param_resolver) + param_resolver = param_resolver or cirq.ParamResolver({}) + solved_circuit = cirq.resolve_parameters(circuit, param_resolver) return self._sample_measure_results(solved_circuit, repetitions) def _sample_measure_results( self, - program: circuits.Circuit, + program: cirq.Circuit, repetitions: int = 1, ) -> Dict[str, np.ndarray]: """Samples from measurement gates in the circuit. @@ -312,13 +301,13 @@ def _sample_measure_results( all_qubits = program.all_qubits() program = qsimc.QSimCircuit( self.noise.noisy_moments(program, sorted(all_qubits)) - if self.noise is not devices.NO_NOISE + if self.noise is not cirq.NO_NOISE else program, device=program.device, ) # Compute indices of measured qubits - ordered_qubits = ops.QubitOrder.DEFAULT.order_for(all_qubits) + ordered_qubits = cirq.QubitOrder.DEFAULT.order_for(all_qubits) num_qubits = len(ordered_qubits) qubit_map = {qubit: index for index, qubit in enumerate(ordered_qubits)} @@ -330,16 +319,16 @@ def _sample_measure_results( measurement_ops = [ op for _, op, _ in program.findall_operations_with_gate_type( - ops.MeasurementGate + cirq.MeasurementGate ) ] - measured_qubits = [] # type: List[ops.Qid] + measured_qubits = [] # type: List[cirq.Qid] bounds = {} # type: Dict[str, Tuple] meas_ops = {} # type: Dict[str, cirq.GateOperation] current_index = 0 for op in measurement_ops: gate = op.gate - key = protocols.measurement_key_name(gate) + key = cirq.measurement_key_name(gate) meas_ops[key] = op if key in bounds: raise ValueError(f"Duplicate MeasurementGate with key {key}") @@ -369,21 +358,21 @@ def _sample_measure_results( # Measurements must be replaced with identity gates to sample properly. # Simply removing them may omit qubits from the circuit. for i in range(len(program.moments)): - program.moments[i] = ops.Moment( + program.moments[i] = cirq.Moment( op - if not isinstance(op.gate, ops.MeasurementGate) - else [ops.IdentityGate(1).on(q) for q in op.qubits] + if not isinstance(op.gate, cirq.MeasurementGate) + else [cirq.IdentityGate(1).on(q) for q in op.qubits] for op in program.moments[i] ) translator_fn_name = "translate_cirq_to_qsim" options["c"] = self._translate_circuit( program, translator_fn_name, - ops.QubitOrder.DEFAULT, + cirq.QubitOrder.DEFAULT, ) options["s"] = self.get_seed() final_state = self._sim_module.qsim_simulate_fullstate(options, 0) - full_results = sim.sample_state_vector( + full_results = cirq.sample_state_vector( final_state.view(np.complex64), range(num_qubits), repetitions=repetitions, @@ -399,7 +388,7 @@ def _sample_measure_results( options["c"] = self._translate_circuit( program, translator_fn_name, - ops.QubitOrder.DEFAULT, + cirq.QubitOrder.DEFAULT, ) for i in range(repetitions): options["s"] = self.get_seed() @@ -412,10 +401,10 @@ def _sample_measure_results( def compute_amplitudes_sweep( self, - program: circuits.Circuit, + program: cirq.Circuit, bitstrings: Sequence[int], - params: study.Sweepable, - qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT, + params: cirq.Sweepable, + qubit_order: cirq.QubitOrderOrList = cirq.QubitOrder.DEFAULT, ) -> Sequence[Sequence[complex]]: """Computes the desired amplitudes using qsim. @@ -440,13 +429,13 @@ def compute_amplitudes_sweep( all_qubits = program.all_qubits() program = qsimc.QSimCircuit( self.noise.noisy_moments(program, sorted(all_qubits)) - if self.noise is not devices.NO_NOISE + if self.noise is not cirq.NO_NOISE else program, device=program.device, ) # qsim numbers qubits in reverse order from cirq - cirq_order = ops.QubitOrder.as_qubit_order(qubit_order).order_for(all_qubits) + cirq_order = cirq.QubitOrder.as_qubit_order(qubit_order).order_for(all_qubits) num_qubits = len(cirq_order) bitstrings = [ format(bitstring, "b").zfill(num_qubits)[::-1] for bitstring in bitstrings @@ -455,7 +444,7 @@ def compute_amplitudes_sweep( options = {"i": "\n".join(bitstrings)} options.update(self.qsim_options) - param_resolvers = study.to_resolvers(params) + param_resolvers = cirq.to_resolvers(params) trials_results = [] if _needs_trajectories(program): @@ -466,7 +455,7 @@ def compute_amplitudes_sweep( simulator_fn = self._sim_module.qsim_simulate for prs in param_resolvers: - solved_circuit = protocols.resolve_parameters(program, prs) + solved_circuit = cirq.resolve_parameters(program, prs) options["c"] = self._translate_circuit( solved_circuit, translator_fn_name, @@ -480,9 +469,9 @@ def compute_amplitudes_sweep( def simulate_sweep( self, - program: circuits.Circuit, - params: study.Sweepable, - qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT, + program: cirq.Circuit, + params: cirq.Sweepable, + qubit_order: cirq.QubitOrderOrList = cirq.QubitOrder.DEFAULT, initial_state: Optional[Union[int, np.ndarray]] = None, ) -> List["SimulationTrialResult"]: """Simulates the supplied Circuit. @@ -523,7 +512,7 @@ def simulate_sweep( all_qubits = program.all_qubits() program = qsimc.QSimCircuit( self.noise.noisy_moments(program, sorted(all_qubits)) - if self.noise is not devices.NO_NOISE + if self.noise is not cirq.NO_NOISE else program, device=program.device, ) @@ -531,9 +520,9 @@ def simulate_sweep( options = {} options.update(self.qsim_options) - param_resolvers = study.to_resolvers(params) + param_resolvers = cirq.to_resolvers(params) # qsim numbers qubits in reverse order from cirq - cirq_order = ops.QubitOrder.as_qubit_order(qubit_order).order_for(all_qubits) + cirq_order = cirq.QubitOrder.as_qubit_order(qubit_order).order_for(all_qubits) qsim_order = list(reversed(cirq_order)) num_qubits = len(qsim_order) if isinstance(initial_state, np.ndarray): @@ -555,7 +544,7 @@ def simulate_sweep( fullstate_simulator_fn = self._sim_module.qsim_simulate_fullstate for prs in param_resolvers: - solved_circuit = protocols.resolve_parameters(program, prs) + solved_circuit = cirq.resolve_parameters(program, prs) options["c"] = self._translate_circuit( solved_circuit, @@ -583,10 +572,10 @@ def simulate_sweep( def simulate_expectation_values_sweep( self, - program: "cirq.Circuit", - observables: Union["cirq.PauliSumLike", List["cirq.PauliSumLike"]], - params: "study.Sweepable", - qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT, + program: cirq.Circuit, + observables: Union[cirq.PauliSumLike, List[cirq.PauliSumLike]], + params: cirq.Sweepable, + qubit_order: cirq.QubitOrderOrList = cirq.QubitOrder.DEFAULT, initial_state: Any = None, permit_terminal_measurements: bool = False, ) -> List[List[float]]: @@ -629,10 +618,10 @@ def simulate_expectation_values_sweep( ) if not isinstance(observables, List): observables = [observables] - psumlist = [ops.PauliSum.wrap(pslike) for pslike in observables] + psumlist = [cirq.PauliSum.wrap(pslike) for pslike in observables] all_qubits = program.all_qubits() - cirq_order = ops.QubitOrder.as_qubit_order(qubit_order).order_for(all_qubits) + cirq_order = cirq.QubitOrder.as_qubit_order(qubit_order).order_for(all_qubits) qsim_order = list(reversed(cirq_order)) num_qubits = len(qsim_order) qubit_map = {qubit: index for index, qubit in enumerate(qsim_order)} @@ -659,7 +648,7 @@ def simulate_expectation_values_sweep( # Add noise to the circuit if a noise model was provided. program = qsimc.QSimCircuit( self.noise.noisy_moments(program, sorted(all_qubits)) - if self.noise is not devices.NO_NOISE + if self.noise is not cirq.NO_NOISE else program, device=program.device, ) @@ -667,7 +656,7 @@ def simulate_expectation_values_sweep( options = {} options.update(self.qsim_options) - param_resolvers = study.to_resolvers(params) + param_resolvers = cirq.to_resolvers(params) if isinstance(initial_state, np.ndarray): if initial_state.dtype != np.complex64: raise TypeError(f"initial_state vector must have dtype np.complex64.") @@ -687,7 +676,7 @@ def simulate_expectation_values_sweep( ev_simulator_fn = self._sim_module.qsim_simulate_expectation_values for prs in param_resolvers: - solved_circuit = protocols.resolve_parameters(program, prs) + solved_circuit = cirq.resolve_parameters(program, prs) options["c"] = self._translate_circuit( solved_circuit, translator_fn_name, @@ -707,7 +696,7 @@ def _translate_circuit( self, circuit: Any, translator_fn_name: str, - qubit_order: ops.QubitOrderOrList, + qubit_order: cirq.QubitOrderOrList, ): # If the circuit is memoized, reuse the corresponding translated # circuit. diff --git a/qsimcirq/qsimh_simulator.py b/qsimcirq/qsimh_simulator.py index 3d2ec049..ccf1c1a0 100644 --- a/qsimcirq/qsimh_simulator.py +++ b/qsimcirq/qsimh_simulator.py @@ -12,15 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union, Sequence +from typing import Sequence -from cirq import study, ops, protocols, circuits, value, SimulatesAmplitudes +import cirq from . import qsim import qsimcirq.qsim_circuit as qsimc -class QSimhSimulator(SimulatesAmplitudes): +class QSimhSimulator(cirq.SimulatesAmplitudes): def __init__(self, qsimh_options: dict = {}): """Creates a new QSimhSimulator using the given options. @@ -41,10 +41,10 @@ def __init__(self, qsimh_options: dict = {}): def compute_amplitudes_sweep( self, - program: circuits.Circuit, + program: cirq.Circuit, bitstrings: Sequence[int], - params: study.Sweepable, - qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT, + params: cirq.Sweepable, + qubit_order: cirq.QubitOrderOrList = cirq.QubitOrder.DEFAULT, ) -> Sequence[Sequence[complex]]: if not isinstance(program, qsimc.QSimCircuit): @@ -58,12 +58,12 @@ def compute_amplitudes_sweep( options = {"i": "\n".join(bitstrings)} options.update(self.qsimh_options) - param_resolvers = study.to_resolvers(params) + param_resolvers = cirq.to_resolvers(params) trials_results = [] for prs in param_resolvers: - solved_circuit = protocols.resolve_parameters(program, prs) + solved_circuit = cirq.resolve_parameters(program, prs) options["c"] = solved_circuit.translate_cirq_to_qsim(qubit_order) From f02260465d7edaf222ac530cf610ca1227cf228e Mon Sep 17 00:00:00 2001 From: Matthew Neeley Date: Fri, 19 Nov 2021 13:51:31 -0800 Subject: [PATCH 162/246] Fix reference to parse_random_state --- qsimcirq/qsim_simulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index a24ffd89..e8faeeca 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -214,7 +214,7 @@ def __init__( 'Keys {"c", "i", "s"} are reserved for internal use and cannot be ' "used in QSimCircuit instantiation." ) - self._prng = cirq.parse_random_state(seed) + self._prng = cirq.value.parse_random_state(seed) self.qsim_options = QSimOptions().as_dict() self.qsim_options.update(qsim_options) self.noise = cirq.NoiseModel.from_noise_model_like(noise) From 3faee5a3a6b10363ddab25490eb465d576889ac9 Mon Sep 17 00:00:00 2001 From: Matthew Neeley Date: Fri, 19 Nov 2021 14:05:28 -0800 Subject: [PATCH 163/246] Fix import of SimulatesExpectationValues --- qsimcirq/qsim_simulator.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index e8faeeca..8e17ee8a 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -18,9 +18,6 @@ import cirq -# TODO: import from cirq directly when fix is released -from cirq.sim.simulator import SimulatesExpectationValues - import numpy as np from . import qsim, qsim_gpu, qsim_custatevec @@ -174,7 +171,7 @@ class QSimSimulator( cirq.SimulatesSamples, cirq.SimulatesAmplitudes, cirq.SimulatesFinalState, - SimulatesExpectationValues, + cirq.SimulatesExpectationValues, ): def __init__( self, From 867d0ad66649b7804978ff192ed1e6c6b5e6e2c0 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 23 Nov 2021 09:12:36 -0800 Subject: [PATCH 164/246] Support invert_mask in qsimcirq --- qsimcirq/qsim_simulator.py | 8 ++++++-- qsimcirq_tests/qsimcirq_test.py | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 8e17ee8a..bd47eb33 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -379,8 +379,9 @@ def _sample_measure_results( for i in range(repetitions): for key, op in meas_ops.items(): meas_indices = [qubit_map[qubit] for qubit in op.qubits] + invert_mask = op.gate.invert_mask for j, q in enumerate(meas_indices): - results[key][i][j] = full_results[i][q] + results[key][i][j] = full_results[i][q] ^ invert_mask[j] else: options["c"] = self._translate_circuit( program, @@ -391,8 +392,11 @@ def _sample_measure_results( options["s"] = self.get_seed() measurements = sampler_fn(options) for key, bound in bounds.items(): + invert_mask = meas_ops[key].gate.invert_mask for j in range(bound[1] - bound[0]): - results[key][i][j] = int(measurements[bound[0] + j]) + results[key][i][j] = int( + measurements[bound[0] + j] ^ invert_mask[j] + ) return results diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 0287d548..048b2ab6 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -361,6 +361,33 @@ def test_cirq_qsim_run(mode: str): assert value.shape == (5, 1) +def test_qsim_invert_mask(): + q0, q1 = cirq.LineQubit.range(2) + circuit = cirq.Circuit( + cirq.measure(q0, q1, key="d", invert_mask=[False, True]), + ) + cirq_sample = cirq.Simulator().sample(circuit, repetitions=5) + qsim_sample = qsimcirq.QSimSimulator().sample(circuit, repetitions=5) + assert qsim_sample.equals(cirq_sample) + + +def test_qsim_invert_mask_intermediate_measure(): + q0, q1 = cirq.LineQubit.range(2) + # The dataframe generated by this should be all zeroes. + circuit = cirq.Circuit( + cirq.measure(q0, q1, key="a", invert_mask=[False, False]), + cirq.X(q0), + cirq.measure(q0, q1, key="b", invert_mask=[True, False]), + cirq.X(q1), + cirq.measure(q0, q1, key="c", invert_mask=[True, True]), + cirq.X(q0), + cirq.measure(q0, q1, key="d", invert_mask=[False, True]), + ) + cirq_sample = cirq.Simulator().sample(circuit, repetitions=5) + qsim_sample = qsimcirq.QSimSimulator().sample(circuit, repetitions=5) + assert qsim_sample.equals(cirq_sample) + + @pytest.mark.parametrize("mode", ["noiseless", "noisy"]) def test_qsim_run_vs_cirq_run(mode: str): # Simple circuit, want to check mapping of qubit(s) to their measurements From fee4f3909bd1ea6ff3f7beb1984c75eeef4d4b5f Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 23 Nov 2021 09:21:13 -0800 Subject: [PATCH 165/246] More coverage --- qsimcirq_tests/qsimcirq_test.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 048b2ab6..2ccccd7d 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -371,6 +371,19 @@ def test_qsim_invert_mask(): assert qsim_sample.equals(cirq_sample) +def test_qsim_invert_mask_different_qubits(): + q0, q1 = cirq.LineQubit.range(2) + circuit = cirq.Circuit( + cirq.measure(q1, key="a", invert_mask=[True]), + cirq.measure(q0, key="b", invert_mask=[True]), + cirq.measure(q0, q1, key="c", invert_mask=[False, True]), + cirq.measure(q1, q0, key="d", invert_mask=[False, True]), + ) + cirq_sample = cirq.Simulator().sample(circuit, repetitions=5) + qsim_sample = qsimcirq.QSimSimulator().sample(circuit, repetitions=5) + assert qsim_sample.equals(cirq_sample) + + def test_qsim_invert_mask_intermediate_measure(): q0, q1 = cirq.LineQubit.range(2) # The dataframe generated by this should be all zeroes. From 25b838c7678dad038f589acf8e1ea4a82ca4367f Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 23 Nov 2021 09:40:26 -0800 Subject: [PATCH 166/246] Rescue tests with full_invert_mask --- qsimcirq/qsim_simulator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index bd47eb33..d4ef424d 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -379,7 +379,7 @@ def _sample_measure_results( for i in range(repetitions): for key, op in meas_ops.items(): meas_indices = [qubit_map[qubit] for qubit in op.qubits] - invert_mask = op.gate.invert_mask + invert_mask = op.gate.full_invert_mask() for j, q in enumerate(meas_indices): results[key][i][j] = full_results[i][q] ^ invert_mask[j] else: @@ -392,7 +392,7 @@ def _sample_measure_results( options["s"] = self.get_seed() measurements = sampler_fn(options) for key, bound in bounds.items(): - invert_mask = meas_ops[key].gate.invert_mask + invert_mask = meas_ops[key].gate.full_invert_mask() for j in range(bound[1] - bound[0]): results[key][i][j] = int( measurements[bound[0] + j] ^ invert_mask[j] From 4ede564da3611174c8f5e9151fe3916b8bb355d9 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 30 Nov 2021 09:31:18 -0800 Subject: [PATCH 167/246] Optimize triple-loops. --- qsimcirq/qsim_simulator.py | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index d4ef424d..b929e441 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -376,27 +376,38 @@ def _sample_measure_results( seed=self._prng, ) - for i in range(repetitions): - for key, op in meas_ops.items(): - meas_indices = [qubit_map[qubit] for qubit in op.qubits] - invert_mask = op.gate.full_invert_mask() - for j, q in enumerate(meas_indices): - results[key][i][j] = full_results[i][q] ^ invert_mask[j] + for key, op in meas_ops.items(): + meas_indices = [qubit_map[qubit] for qubit in op.qubits] + invert_mask = op.gate.full_invert_mask() + for j, q in enumerate(meas_indices): + results[key][:, j] = np.logical_xor( + full_results[:, q], + invert_mask[j], + ) + else: options["c"] = self._translate_circuit( program, translator_fn_name, cirq.QubitOrder.DEFAULT, ) + measurements = np.empty( + shape=( + repetitions, + sum(cirq.num_qubits(op) for op in meas_ops.values()) + ) + ) for i in range(repetitions): options["s"] = self.get_seed() - measurements = sampler_fn(options) - for key, bound in bounds.items(): - invert_mask = meas_ops[key].gate.full_invert_mask() - for j in range(bound[1] - bound[0]): - results[key][i][j] = int( - measurements[bound[0] + j] ^ invert_mask[j] - ) + measurements[i] = sampler_fn(options) + + for key, bound in bounds.items(): + invert_mask = meas_ops[key].gate.full_invert_mask() + for j in range(bound[1] - bound[0]): + results[key][:, j] = np.logical_xor( + measurements[:, bound[0] + j], + invert_mask[j] + ) return results From 3019cf205e5c79609f0281c30e1263dd70d9d503 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 30 Nov 2021 09:54:00 -0800 Subject: [PATCH 168/246] Further optimization for single-pass circuits. --- qsimcirq/qsim_simulator.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index b929e441..f68ef227 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -379,11 +379,9 @@ def _sample_measure_results( for key, op in meas_ops.items(): meas_indices = [qubit_map[qubit] for qubit in op.qubits] invert_mask = op.gate.full_invert_mask() - for j, q in enumerate(meas_indices): - results[key][:, j] = np.logical_xor( - full_results[:, q], - invert_mask[j], - ) + # Match result order to order in ops, then apply invert mask + permuted_results = full_results[:, meas_indices] + results[key] = np.logical_xor(permuted_results, invert_mask) else: options["c"] = self._translate_circuit( From 15e661b385ea044fe185cfa0fc0a779f1106a5cb Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 30 Nov 2021 10:23:37 -0800 Subject: [PATCH 169/246] Further optimization for multi-pass circuits. --- qsimcirq/qsim_simulator.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index f68ef227..0d0ff1e2 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -401,11 +401,9 @@ def _sample_measure_results( for key, bound in bounds.items(): invert_mask = meas_ops[key].gate.full_invert_mask() - for j in range(bound[1] - bound[0]): - results[key][:, j] = np.logical_xor( - measurements[:, bound[0] + j], - invert_mask[j] - ) + permutation = list(range(bound[0], bound[1])) + permuted_measurements = measurements[:, permutation] + results[key] = np.logical_xor(permuted_measurements, invert_mask) return results From cf4e1d4ea9dd2252147abdf754cd5887af4ce82e Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 30 Nov 2021 10:39:09 -0800 Subject: [PATCH 170/246] formatting --- qsimcirq/qsim_simulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 0d0ff1e2..904b06d1 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -392,7 +392,7 @@ def _sample_measure_results( measurements = np.empty( shape=( repetitions, - sum(cirq.num_qubits(op) for op in meas_ops.values()) + sum(cirq.num_qubits(op) for op in meas_ops.values()), ) ) for i in range(repetitions): From 4cb6217df8298a70b000c7949d44ca99419c7fe8 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Tue, 30 Nov 2021 23:10:21 +0100 Subject: [PATCH 171/246] Update simulators. --- lib/BUILD | 18 +- lib/simulator.h | 471 ++ lib/simulator_avx.h | 7887 ++------------------- lib/simulator_avx512.h | 8724 +----------------------- lib/simulator_basic.h | 1233 +--- lib/simulator_sse.h | 6071 ++--------------- pybind_interface/Makefile | 2 +- pybind_interface/avx512/CMakeLists.txt | 2 +- tests/make.sh | 15 +- 9 files changed, 2014 insertions(+), 22409 deletions(-) create mode 100644 lib/simulator.h diff --git a/lib/BUILD b/lib/BUILD index 99c93e96..e40a58d3 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -40,6 +40,7 @@ cc_library( "run_qsimh.h", "seqfor.h", "simmux.h", + "simulator.h", "simulator_avx.h", "simulator_avx512.h", "simulator_basic.h", @@ -97,6 +98,7 @@ cc_library( "run_qsimh.h", "seqfor.h", "simmux.h", + "simulator.h", "simulator_avx.h", "simulator_avx512.h", "simulator_basic.h", @@ -149,6 +151,7 @@ cc_library( "run_qsim.h", "seqfor.h", "simmux.h", + "simulator.h", "simulator_avx.h", "simulator_avx512.h", "simulator_basic.h", @@ -194,6 +197,7 @@ cc_library( "run_qsimh.h", "seqfor.h", "simmux.h", + "simulator.h", "simulator_avx.h", "simulator_avx512.h", "simulator_basic.h", @@ -465,11 +469,17 @@ cc_library( ### Simulator libraries ### +cc_library( + name = "simulator_base", + hdrs = ["simulator.h"], + deps = [":bits"], +) + cc_library( name = "simulator_avx", hdrs = ["simulator_avx.h"], deps = [ - ":bits", + ":simulator_base", ":statespace_avx", ], ) @@ -478,7 +488,7 @@ cc_library( name = "simulator_avx512", hdrs = ["simulator_avx512.h"], deps = [ - ":bits", + ":simulator_base", ":statespace_avx512", ], ) @@ -487,7 +497,7 @@ cc_library( name = "simulator_basic", hdrs = ["simulator_basic.h"], deps = [ - ":bits", + ":simulator_base", ":statespace_basic", ], ) @@ -496,7 +506,7 @@ cc_library( name = "simulator_sse", hdrs = ["simulator_sse.h"], deps = [ - ":bits", + ":simulator_base", ":statespace_sse", ], ) diff --git a/lib/simulator.h b/lib/simulator.h new file mode 100644 index 00000000..a5b799f5 --- /dev/null +++ b/lib/simulator.h @@ -0,0 +1,471 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SIMULATOR_H_ +#define SIMULATOR_H_ + +#include + +#include "bits.h" + +namespace qsim { + +/** + * Base class for simulator classes. + */ +class SimulatorBase { + protected: + template + static void FillIndices(unsigned num_qubits, const std::vector& qs, + uint64_t* ms, uint64_t* xss) { + constexpr unsigned hsize = 1 << H; + + uint64_t xs[H]; + + xs[0] = uint64_t{1} << (qs[L] + 1); + ms[0] = (uint64_t{1} << qs[L]) - 1; + for (unsigned i = 1; i < H; ++i) { + xs[i] = uint64_t{1} << (qs[L + i] + 1); + ms[i] = ((uint64_t{1} << qs[L + i]) - 1) ^ (xs[i - 1] - 1); + } + ms[H] = ((uint64_t{1} << num_qubits) - 1) ^ (xs[H - 1] - 1); + + for (unsigned i = 0; i < hsize; ++i) { + uint64_t a = 0; + for (uint64_t k = 0; k < H; ++k) { + a += xs[k] * ((i >> k) & 1); + } + xss[i] = a; + } + } + + template + static void FillMatrix(unsigned qmaskl, const fp_type* matrix, fp_type* w) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; + constexpr unsigned rsize = 1 << R; + + unsigned s = 0; + + for (unsigned i = 0; i < hsize; ++i) { + for (unsigned j = 0; j < gsize; ++j) { + unsigned p0 = 2 * i * lsize * gsize + 2 * lsize * (j / lsize); + + for (unsigned k = 0; k < rsize; ++k) { + unsigned l = bits::CompressBits(k, R, qmaskl); + unsigned p = p0 + 2 * (gsize * l + (j + l) % lsize); + + w[s + 0] = matrix[p]; + w[s + rsize] = matrix[p + 1]; + + ++s; + } + + s += rsize; + } + } + } + + template + static void FillControlledMatrixH(uint64_t cvalsl, uint64_t cmaskl, + const fp_type* matrix, fp_type* w) { + constexpr unsigned hsize = 1 << H; + constexpr unsigned rsize = 1 << R; + + unsigned s = 0; + + for (unsigned i = 0; i < hsize; ++i) { + for (unsigned j = 0; j < hsize; ++j) { + unsigned p = hsize * i + j; + fp_type v = i == j ? 1 : 0; + + for (unsigned k = 0; k < rsize; ++k) { + w[s] = cvalsl == (k & cmaskl) ? matrix[2 * p] : v; + w[s + rsize] = cvalsl == (k & cmaskl) ? matrix[2 * p + 1] : 0; + + ++s; + } + + s += rsize; + } + } + } + + template + static void FillControlledMatrixL(uint64_t cvalsl, uint64_t cmaskl, + unsigned qmaskl, const fp_type* matrix, + fp_type* w) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; + constexpr unsigned rsize = 1 << R; + + unsigned s = 0; + + for (unsigned i = 0; i < hsize; ++i) { + for (unsigned j = 0; j < gsize; ++j) { + unsigned p0 = i * lsize * gsize + lsize * (j / lsize); + + for (unsigned k = 0; k < rsize; ++k) { + unsigned l = bits::CompressBits(k, R, qmaskl); + unsigned p = p0 + gsize * l + (j + l) % lsize; + + fp_type v = p / gsize == p % gsize ? 1 : 0; + + w[s] = cvalsl == (k & cmaskl) ? matrix[2 * p] : v; + w[s + rsize] = cvalsl == (k & cmaskl) ? matrix[2 * p + 1] : 0; + + ++s; + } + + s += rsize; + } + } + } + + struct Masks1 { + uint64_t imaskh; + uint64_t qmaskh; + }; + + template + static Masks1 GetMasks1(const std::vector& qs) { + uint64_t qmaskh = 0; + + for (unsigned i = 0; i < H; ++i) { + qmaskh |= uint64_t{1} << qs[i]; + } + + return {2 * (~qmaskh ^ ((1 << R) - 1)), 2 * qmaskh}; + } + + struct Masks2 { + uint64_t imaskh; + uint64_t qmaskh; + unsigned qmaskl; + }; + + template + static Masks2 GetMasks2(const std::vector& qs) { + uint64_t qmaskh = 0; + unsigned qmaskl = 0; + + for (unsigned i = 0; i < L; ++i) { + qmaskl |= 1 << qs[i]; + } + + for (unsigned i = L; i < H + L; ++i) { + qmaskh |= uint64_t{1} << qs[i]; + } + + return {2 * (~qmaskh ^ ((1 << R) - 1)), 2 * qmaskh, qmaskl}; + } + + struct Masks3 { + uint64_t imaskh; + uint64_t qmaskh; + uint64_t cvalsh; + }; + + template + static Masks3 GetMasks3(unsigned num_qubits, const std::vector& qs, + const std::vector& cqs, uint64_t cvals) { + uint64_t qmaskh = 0; + uint64_t cmaskh = 0; + + for (unsigned i = 0; i < H; ++i) { + qmaskh |= uint64_t{1} << qs[i]; + } + + for (auto q : cqs) { + cmaskh |= uint64_t{1} << q; + } + + uint64_t cvalsh = bits::ExpandBits(cvals, num_qubits, cmaskh); + + uint64_t maskh = ~(qmaskh | cmaskh) ^ ((1 << R) - 1); + + return {2 * maskh, 2 * qmaskh, 2 * cvalsh}; + } + + struct Masks4 { + uint64_t imaskh; + uint64_t qmaskh; + uint64_t cvalsh; + uint64_t cvalsl; + uint64_t cmaskl; + unsigned cl; + }; + + template + static Masks4 GetMasks4(unsigned num_qubits, const std::vector& qs, + const std::vector& cqs, uint64_t cvals) { + unsigned cl = 0; + uint64_t qmaskh = 0; + uint64_t cmaskh = 0; + uint64_t cmaskl = 0; + + for (unsigned i = 0; i < H; ++i) { + qmaskh |= uint64_t{1} << qs[i]; + } + + for (auto q : cqs) { + if (q >= R) { + cmaskh |= uint64_t{1} << q; + } else { + ++cl; + cmaskl |= uint64_t{1} << q; + } + } + + uint64_t cvalsh = bits::ExpandBits(cvals >> cl, num_qubits, cmaskh); + uint64_t cvalsl = bits::ExpandBits(cvals & ((1 << cl) - 1), R, cmaskl); + + uint64_t maskh = ~(qmaskh | cmaskh) ^ ((1 << R) - 1); + + return {2 * maskh, 2 * qmaskh, 2 * cvalsh, cvalsl, cmaskl, cl}; + } + + struct Masks5 { + uint64_t imaskh; + uint64_t qmaskh; + uint64_t cvalsh; + unsigned qmaskl; + }; + + template + static Masks5 GetMasks5(unsigned num_qubits, const std::vector& qs, + const std::vector& cqs, uint64_t cvals) { + uint64_t qmaskh = 0; + uint64_t cmaskh = 0; + unsigned qmaskl = 0; + + for (unsigned i = 0; i < L; ++i) { + qmaskl |= 1 << qs[i]; + } + + for (unsigned i = L; i < H + L; ++i) { + qmaskh |= uint64_t{1} << qs[i]; + } + + for (auto q : cqs) { + cmaskh |= uint64_t{1} << q; + } + + uint64_t cvalsh = bits::ExpandBits(cvals, num_qubits, cmaskh); + + uint64_t maskh = ~(qmaskh | cmaskh) ^ ((1 << R) - 1); + + return {2 * maskh, 2 * qmaskh, 2 * cvalsh, qmaskl}; + } + + struct Masks6 { + uint64_t imaskh; + uint64_t qmaskh; + uint64_t cvalsh; + uint64_t cvalsl; + uint64_t cmaskl; + unsigned qmaskl; + unsigned cl; + }; + + template + static Masks6 GetMasks6(unsigned num_qubits, const std::vector& qs, + const std::vector& cqs, uint64_t cvals) { + unsigned cl = 0; + uint64_t qmaskh = 0; + uint64_t cmaskh = 0; + uint64_t cmaskl = 0; + unsigned qmaskl = 0; + + for (unsigned i = 0; i < L; ++i) { + qmaskl |= 1 << qs[i]; + } + + for (unsigned i = L; i < H + L; ++i) { + qmaskh |= uint64_t{1} << qs[i]; + } + + for (auto q : cqs) { + if (q >= R) { + cmaskh |= uint64_t{1} << q; + } else { + ++cl; + cmaskl |= uint64_t{1} << q; + } + } + + uint64_t cvalsh = bits::ExpandBits(cvals >> cl, num_qubits, cmaskh); + uint64_t cvalsl = bits::ExpandBits(cvals & ((1 << cl) - 1), R, cmaskl); + + uint64_t maskh = ~(qmaskh | cmaskh) ^ ((1 << R) - 1); + + return {2 * maskh, 2 * qmaskh, 2 * cvalsh, cvalsl, cmaskl, qmaskl, cl}; + } + + struct Masks7 { + uint64_t cvalsh; + uint64_t cmaskh; + }; + + static Masks7 GetMasks7(unsigned num_qubits, const std::vector& qs, + const std::vector& cqs, uint64_t cvals) { + uint64_t cmaskh = 0; + + for (auto q : cqs) { + cmaskh |= uint64_t{1} << q; + } + + uint64_t cvalsh = bits::ExpandBits(cvals, num_qubits, cmaskh); + + return {cvalsh, cmaskh}; + } + + struct Masks8 { + uint64_t cvalsh; + uint64_t cmaskh; + uint64_t cvalsl; + uint64_t cmaskl; + }; + + template + static Masks8 GetMasks8(unsigned num_qubits, const std::vector& qs, + const std::vector& cqs, uint64_t cvals) { + unsigned cl = 0; + uint64_t cmaskh = 0; + uint64_t cmaskl = 0; + + for (auto q : cqs) { + if (q >= R) { + cmaskh |= uint64_t{1} << q; + } else { + ++cl; + cmaskl |= uint64_t{1} << q; + } + } + + uint64_t cvalsh = bits::ExpandBits(cvals >> cl, num_qubits, cmaskh); + uint64_t cvalsl = bits::ExpandBits(cvals & ((1 << cl) - 1), R, cmaskl); + + return {cvalsh, cmaskh, cvalsl, cmaskl}; + } + + struct Masks9 { + uint64_t cvalsh; + uint64_t cmaskh; + unsigned qmaskl; + }; + + template + static Masks9 GetMasks9(unsigned num_qubits, const std::vector& qs, + const std::vector& cqs, uint64_t cvals) { + uint64_t cmaskh = 0; + unsigned qmaskl = 0; + + for (unsigned i = 0; i < L; ++i) { + qmaskl |= 1 << qs[i]; + } + + for (auto q : cqs) { + cmaskh |= uint64_t{1} << q; + } + + uint64_t cvalsh = bits::ExpandBits(cvals, num_qubits, cmaskh); + + return {cvalsh, cmaskh, qmaskl}; + } + + struct Masks10 { + uint64_t cvalsh; + uint64_t cmaskh; + uint64_t cvalsl; + uint64_t cmaskl; + unsigned qmaskl; + }; + + template + static Masks10 GetMasks10(unsigned num_qubits, + const std::vector& qs, + const std::vector& cqs, uint64_t cvals) { + unsigned cl = 0; + uint64_t cmaskh = 0; + uint64_t cmaskl = 0; + unsigned qmaskl = 0; + + for (unsigned i = 0; i < L; ++i) { + qmaskl |= 1 << qs[i]; + } + + for (auto q : cqs) { + if (q >= R) { + cmaskh |= uint64_t{1} << q; + } else { + ++cl; + cmaskl |= uint64_t{1} << q; + } + } + + uint64_t cvalsh = bits::ExpandBits(cvals >> cl, num_qubits, cmaskh); + uint64_t cvalsl = bits::ExpandBits(cvals & ((1 << cl) - 1), R, cmaskl); + + return {cvalsh, cmaskh, cvalsl, cmaskl, qmaskl}; + } + + template + static unsigned GetQMask(const std::vector& qs) { + unsigned qmaskl = 0; + + for (unsigned i = 0; i < L; ++i) { + qmaskl |= 1 << qs[i]; + } + + return qmaskl; + } + + template + static unsigned MaskedAdd( + unsigned a, unsigned b, unsigned mask, unsigned lsize) { + unsigned c = bits::CompressBits(a, R, mask); + return bits::ExpandBits((c + b) % lsize, R, mask); + } +}; + +template <> +inline void SimulatorBase::FillIndices<0, 1>(unsigned num_qubits, + const std::vector& qs, + uint64_t* ms, uint64_t* xss) { + ms[0] = -1; + xss[0] = 0; +} + +template <> +inline void SimulatorBase::FillIndices<0, 2>(unsigned num_qubits, + const std::vector& qs, + uint64_t* ms, uint64_t* xss) { + ms[0] = -1; + xss[0] = 0; +} + +template <> +inline void SimulatorBase::FillIndices<0, 3>(unsigned num_qubits, + const std::vector& qs, + uint64_t* ms, uint64_t* xss) { + ms[0] = -1; + xss[0] = 0; +} + +} // namespace qsim + +#endif // SIMULATOR_H_ diff --git a/lib/simulator_avx.h b/lib/simulator_avx.h index f0dbaa5e..cb596808 100644 --- a/lib/simulator_avx.h +++ b/lib/simulator_avx.h @@ -17,11 +17,12 @@ #include -#include #include #include +#include +#include -#include "bits.h" +#include "simulator.h" #include "statespace_avx.h" namespace qsim { @@ -30,7 +31,7 @@ namespace qsim { * Quantum circuit simulator with AVX vectorization. */ template -class SimulatorAVX final { +class SimulatorAVX final : public SimulatorBase { public: using StateSpace = StateSpaceAVX; using State = typename StateSpace::State; @@ -52,62 +53,62 @@ class SimulatorAVX final { switch (qs.size()) { case 1: if (qs[0] > 2) { - ApplyGate1H(qs, matrix, state); + ApplyGateH<1>(qs, matrix, state); } else { - ApplyGate1L(qs, matrix, state); + ApplyGateL<0, 1>(qs, matrix, state); } break; case 2: if (qs[0] > 2) { - ApplyGate2HH(qs, matrix, state); + ApplyGateH<2>(qs, matrix, state); } else if (qs[1] > 2) { - ApplyGate2HL(qs, matrix, state); + ApplyGateL<1, 1>(qs, matrix, state); } else { - ApplyGate2LL(qs, matrix, state); + ApplyGateL<0, 2>(qs, matrix, state); } break; case 3: if (qs[0] > 2) { - ApplyGate3HHH(qs, matrix, state); + ApplyGateH<3>(qs, matrix, state); } else if (qs[1] > 2) { - ApplyGate3HHL(qs, matrix, state); + ApplyGateL<2, 1>(qs, matrix, state); } else if (qs[2] > 2) { - ApplyGate3HLL(qs, matrix, state); + ApplyGateL<1, 2>(qs, matrix, state); } else { - ApplyGate3LLL(qs, matrix, state); + ApplyGateL<0, 3>(qs, matrix, state); } break; case 4: if (qs[0] > 2) { - ApplyGate4HHHH(qs, matrix, state); + ApplyGateH<4>(qs, matrix, state); } else if (qs[1] > 2) { - ApplyGate4HHHL(qs, matrix, state); + ApplyGateL<3, 1>(qs, matrix, state); } else if (qs[2] > 2) { - ApplyGate4HHLL(qs, matrix, state); + ApplyGateL<2, 2>(qs, matrix, state); } else { - ApplyGate4HLLL(qs, matrix, state); + ApplyGateL<1, 3>(qs, matrix, state); } break; case 5: if (qs[0] > 2) { - ApplyGate5HHHHH(qs, matrix, state); + ApplyGateH<5>(qs, matrix, state); } else if (qs[1] > 2) { - ApplyGate5HHHHL(qs, matrix, state); + ApplyGateL<4, 1>(qs, matrix, state); } else if (qs[2] > 2) { - ApplyGate5HHHLL(qs, matrix, state); + ApplyGateL<3, 2>(qs, matrix, state); } else { - ApplyGate5HHLLL(qs, matrix, state); + ApplyGateL<2, 3>(qs, matrix, state); } break; case 6: if (qs[0] > 2) { - ApplyGate6HHHHHH(qs, matrix, state); + ApplyGateH<6>(qs, matrix, state); } else if (qs[1] > 2) { - ApplyGate6HHHHHL(qs, matrix, state); + ApplyGateL<5, 1>(qs, matrix, state); } else if (qs[2] > 2) { - ApplyGate6HHHHLL(qs, matrix, state); + ApplyGateL<4, 2>(qs, matrix, state); } else { - ApplyGate6HHHLLL(qs, matrix, state); + ApplyGateL<3, 3>(qs, matrix, state); } break; default: @@ -120,13 +121,16 @@ class SimulatorAVX final { * Applies a controlled gate using AVX instructions. * @param qs Indices of the qubits affected by this gate. * @param cqs Indices of control qubits. - * @param cmask Bit mask of control qubit values. + * @param cvals Bit mask of control qubit values. * @param matrix Matrix representation of the gate to be applied. * @param state The state of the system, to be updated by this method. */ void ApplyControlledGate(const std::vector& qs, - const std::vector& cqs, uint64_t cmask, + const std::vector& cqs, uint64_t cvals, const fp_type* matrix, State& state) const { + // Assume qs[0] < qs[1] < qs[2] < ... . + // Assume cqs[0] < cqs[1] < cqs[2] < ... . + if (cqs.size() == 0) { ApplyGate(qs, matrix, state); return; @@ -136,90 +140,90 @@ class SimulatorAVX final { case 1: if (qs[0] > 2) { if (cqs[0] > 2) { - ApplyControlledGate1H_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate1H_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<1>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 2) { - ApplyControlledGate1L_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate1L_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 1, 0>(qs, cqs, cvals, matrix, state); } } break; case 2: if (qs[0] > 2) { if (cqs[0] > 2) { - ApplyControlledGate2HH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<2>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2HH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<2>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 2) { if (cqs[0] > 2) { - ApplyControlledGate2HL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2HL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 1, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 2) { - ApplyControlledGate2LL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2LL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 2, 0>(qs, cqs, cvals, matrix, state); } } break; case 3: if (qs[0] > 2) { if (cqs[0] > 2) { - ApplyControlledGate3HHH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<3>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HHH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<3>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 2) { if (cqs[0] > 2) { - ApplyControlledGate3HHL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HHL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 1, 0>(qs, cqs, cvals, matrix, state); } } else if (qs[2] > 2) { if (cqs[0] > 2) { - ApplyControlledGate3HLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 2, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 2) { - ApplyControlledGate3LLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 3, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3LLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 3, 0>(qs, cqs, cvals, matrix, state); } } break; case 4: if (qs[0] > 2) { if (cqs[0] > 2) { - ApplyControlledGate4HHHH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<4>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHHH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<4>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 2) { if (cqs[0] > 2) { - ApplyControlledGate4HHHL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<3, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHHL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<3, 1, 0>(qs, cqs, cvals, matrix, state); } } else if (qs[2] > 2) { if (cqs[0] > 2) { - ApplyControlledGate4HHLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 2, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 2) { - ApplyControlledGate4HLLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 3, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HLLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 3, 0>(qs, cqs, cvals, matrix, state); } } break; @@ -244,62 +248,62 @@ class SimulatorAVX final { switch (qs.size()) { case 1: if (qs[0] > 2) { - return ExpectationValue1H(qs, matrix, state); + return ExpectationValueH<1>(qs, matrix, state); } else { - return ExpectationValue1L(qs, matrix, state); + return ExpectationValueL<0, 1>(qs, matrix, state); } break; case 2: if (qs[0] > 2) { - return ExpectationValue2HH(qs, matrix, state); + return ExpectationValueH<2>(qs, matrix, state); } else if (qs[1] > 2) { - return ExpectationValue2HL(qs, matrix, state); + return ExpectationValueL<1, 1>(qs, matrix, state); } else { - return ExpectationValue2LL(qs, matrix, state); + return ExpectationValueL<0, 2>(qs, matrix, state); } break; case 3: if (qs[0] > 2) { - return ExpectationValue3HHH(qs, matrix, state); + return ExpectationValueH<3>(qs, matrix, state); } else if (qs[1] > 2) { - return ExpectationValue3HHL(qs, matrix, state); + return ExpectationValueL<2, 1>(qs, matrix, state); } else if (qs[2] > 2) { - return ExpectationValue3HLL(qs, matrix, state); + return ExpectationValueL<1, 2>(qs, matrix, state); } else { - return ExpectationValue3LLL(qs, matrix, state); + return ExpectationValueL<0, 3>(qs, matrix, state); } break; case 4: if (qs[0] > 2) { - return ExpectationValue4HHHH(qs, matrix, state); + return ExpectationValueH<4>(qs, matrix, state); } else if (qs[1] > 2) { - return ExpectationValue4HHHL(qs, matrix, state); + return ExpectationValueL<3, 1>(qs, matrix, state); } else if (qs[2] > 2) { - return ExpectationValue4HHLL(qs, matrix, state); + return ExpectationValueL<2, 2>(qs, matrix, state); } else { - return ExpectationValue4HLLL(qs, matrix, state); + return ExpectationValueL<1, 3>(qs, matrix, state); } break; case 5: if (qs[0] > 2) { - return ExpectationValue5HHHHH(qs, matrix, state); + return ExpectationValueH<5>(qs, matrix, state); } else if (qs[1] > 2) { - return ExpectationValue5HHHHL(qs, matrix, state); + return ExpectationValueL<4, 1>(qs, matrix, state); } else if (qs[2] > 2) { - return ExpectationValue5HHHLL(qs, matrix, state); + return ExpectationValueL<3, 2>(qs, matrix, state); } else { - return ExpectationValue5HHLLL(qs, matrix, state); + return ExpectationValueL<2, 3>(qs, matrix, state); } break; case 6: if (qs[0] > 2) { - return ExpectationValue6HHHHHH(qs, matrix, state); + return ExpectationValueH<6>(qs, matrix, state); } else if (qs[1] > 2) { - return ExpectationValue6HHHHHL(qs, matrix, state); + return ExpectationValueL<5, 1>(qs, matrix, state); } else if (qs[2] > 2) { - return ExpectationValue6HHHHLL(qs, matrix, state); + return ExpectationValueL<4, 2>(qs, matrix, state); } else { - return ExpectationValue6HHHLLL(qs, matrix, state); + return ExpectationValueL<3, 3>(qs, matrix, state); } break; default: @@ -318,44 +322,31 @@ class SimulatorAVX final { } private: - void ApplyGate1H(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } +#ifdef __BMI2__ + + template + void ApplyGateH(const std::vector& qs, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { + uint64_t imaskh, uint64_t qmaskh, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m256 ru, iu, rn, in; - __m256 rs[2], is[2]; + __m256 rs[hsize], is[hsize]; - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]); + auto p0 = rstate + _pdep_u64(i, imaskh); - auto p0 = rstate + 2 * k; + for (unsigned k = 0; k < hsize; ++k) { + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); + rs[k] = _mm256_load_ps(p0 + p); + is[k] = _mm256_load_ps(p0 + p + 8); } uint64_t j = 0; - for (unsigned l = 0; l < 2; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm256_set1_ps(v[j]); iu = _mm256_set1_ps(v[j + 1]); rn = _mm256_mul_ps(rs[0], ru); @@ -365,89 +356,64 @@ class SimulatorAVX final { j += 2; - for (unsigned n = 1; n < 2; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm256_set1_ps(v[j]); iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); + rn = _mm256_fmadd_ps(rs[l], ru, rn); + in = _mm256_fmadd_ps(rs[l], iu, in); + rn = _mm256_fnmadd_ps(is[l], iu, rn); + in = _mm256_fmadd_ps(is[l], ru, in); j += 2; } - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm256_store_ps(p0 + p, rn); + _mm256_store_ps(p0 + p + 8, in); } }; - fp_type* rstate = state.get(); + auto m = GetMasks1(qs); - unsigned k = 4; + unsigned k = 3 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, matrix, ms, xss, rstate); + for_.Run(size, f, matrix, m.imaskh, m.qmaskh, state.get()); } - void ApplyGate1L(const std::vector& qs, - const fp_type* matrix, State& state) const { - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(4); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - + template + void ApplyGateL(const std::vector& qs, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const __m256i* idx, fp_type* rstate) { + uint64_t imaskh, uint64_t qmaskh, const __m256i* idx, + fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; + __m256 rn, in; - __m256 rs[2], is[2]; + __m256 rs[gsize], is[gsize]; + + auto p0 = rstate + _pdep_u64(i, imaskh); - auto p0 = rstate + 16 * i; + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm256_load_ps(p0); - is[2 * l] = _mm256_load_ps(p0 + 8); + rs[k2] = _mm256_load_ps(p0 + p); + is[k2] = _mm256_load_ps(p0 + p + 8); - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm256_permutevar8x32_ps(rs[k2], idx[l - 1]); + is[k2 + l] = _mm256_permutevar8x32_ps(is[k2], idx[l - 1]); } } uint64_t j = 0; - for (unsigned l = 0; l < 1; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm256_mul_ps(rs[0], w[j]); in = _mm256_mul_ps(rs[0], w[j + 1]); rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); @@ -455,71 +421,60 @@ class SimulatorAVX final { j += 2; - for (unsigned n = 1; n < 2; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm256_fmadd_ps(rs[l], w[j], rn); + in = _mm256_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm256_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm256_fmadd_ps(is[l], w[j], in); j += 2; } - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm256_store_ps(p0 + p, rn); + _mm256_store_ps(p0 + p + 8, in); } }; - fp_type* rstate = state.get(); + __m256i idx[1 << L]; + __m256 w[1 << (1 + 2 * H + L)]; - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + auto m = GetMasks2(qs); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); + + unsigned r = 3 + H; + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, w, idx, rstate); + for_.Run(size, f, w, m.imaskh, m.qmaskh, idx, state.get()); } - void ApplyGate2HH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - + template + void ApplyControlledGateHH(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, + uint64_t imaskh, uint64_t qmaskh, uint64_t cvalsh, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m256 ru, iu, rn, in; - __m256 rs[4], is[4]; + __m256 rs[hsize], is[hsize]; - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]); + auto p0 = rstate + (_pdep_u64(i, imaskh) | cvalsh); - auto p0 = rstate + 2 * k; + for (unsigned k = 0; k < hsize; ++k) { + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); + rs[k] = _mm256_load_ps(p0 + p); + is[k] = _mm256_load_ps(p0 + p + 8); } uint64_t j = 0; - for (unsigned l = 0; l < 4; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm256_set1_ps(v[j]); iu = _mm256_set1_ps(v[j + 1]); rn = _mm256_mul_ps(rs[0], ru); @@ -529,110 +484,124 @@ class SimulatorAVX final { j += 2; - for (unsigned n = 1; n < 4; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm256_set1_ps(v[j]); iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); + rn = _mm256_fmadd_ps(rs[l], ru, rn); + in = _mm256_fmadd_ps(rs[l], iu, in); + rn = _mm256_fnmadd_ps(is[l], iu, rn); + in = _mm256_fmadd_ps(is[l], ru, in); j += 2; } - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm256_store_ps(p0 + p, rn); + _mm256_store_ps(p0 + p + 8, in); } }; - fp_type* rstate = state.get(); + auto m = GetMasks3(state.num_qubits(), qs, cqs, cvals); - unsigned k = 5; + unsigned k = 3 + H + cqs.size(); unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, matrix, ms, xss, rstate); + for_.Run(size, f, matrix, m.imaskh, m.qmaskh, m.cvalsh, state.get()); } - void ApplyGate2HL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } + template + void ApplyControlledGateHL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, + uint64_t imaskh, uint64_t qmaskh, uint64_t cvalsh, + fp_type* rstate) { + constexpr unsigned hsize = 1 << H; - unsigned p[8]; - __m256i idx[1]; + __m256 rn, in; + __m256 rs[hsize], is[hsize]; - auto s = StateSpace::Create(6); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; + auto p0 = rstate + (_pdep_u64(i, imaskh) | cvalsh); - unsigned qmask = (1 << qs[0]); + for (unsigned k = 0; k < hsize; ++k) { + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); + rs[k] = _mm256_load_ps(p0 + p); + is[k] = _mm256_load_ps(p0 + p + 8); } - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } + uint64_t j = 0; - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } + for (unsigned k = 0; k < hsize; ++k) { + rn = _mm256_mul_ps(rs[0], w[j]); + in = _mm256_mul_ps(rs[0], w[j + 1]); + rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); + in = _mm256_fmadd_ps(is[0], w[j], in); + + j += 2; - unsigned l = 2 * (4 * i + m); + for (unsigned l = 1; l < hsize; ++l) { + rn = _mm256_fmadd_ps(rs[l], w[j], rn); + in = _mm256_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm256_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm256_fmadd_ps(is[l], w[j], in); - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; + j += 2; } - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } + uint64_t p = _pdep_u64(k, qmaskh); + + _mm256_store_ps(p0 + p, rn); + _mm256_store_ps(p0 + p + 8, in); } - } + }; + + __m256 w[1 << (1 + 2 * H)]; + + auto m = GetMasks4(state.num_qubits(), qs, cqs, cvals); + FillControlledMatrixH(m.cvalsl, m.cmaskl, matrix, (fp_type*) w); + + unsigned r = 3 + H + cqs.size() - m.cl; + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; + uint64_t size = uint64_t{1} << n; + + for_.Run(size, f, w, m.imaskh, m.qmaskh, m.cvalsh, state.get()); + } + template + void ApplyControlledGateL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, + uint64_t imaskh, uint64_t qmaskh, uint64_t cvalsh, const __m256i* idx, fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; + __m256 rn, in; - __m256 rs[4], is[4]; + __m256 rs[gsize], is[gsize]; - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]); + auto p0 = rstate + (_pdep_u64(i, imaskh) | cvalsh); - auto p0 = rstate + 2 * k; + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); + rs[k2] = _mm256_load_ps(p0 + p); + is[k2] = _mm256_load_ps(p0 + p + 8); - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm256_permutevar8x32_ps(rs[k2], idx[l - 1]); + is[k2 + l] = _mm256_permutevar8x32_ps(is[k2], idx[l - 1]); } } uint64_t j = 0; - for (unsigned l = 0; l < 2; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm256_mul_ps(rs[0], w[j]); in = _mm256_mul_ps(rs[0], w[j + 1]); rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); @@ -640,87 +609,149 @@ class SimulatorAVX final { j += 2; - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm256_fmadd_ps(rs[l], w[j], rn); + in = _mm256_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm256_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm256_fmadd_ps(is[l], w[j], in); j += 2; } - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm256_store_ps(p0 + p, rn); + _mm256_store_ps(p0 + p + 8, in); } }; - fp_type* rstate = state.get(); + __m256i idx[1 << L]; + __m256 w[1 << (1 + 2 * H + L)]; - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; + if (CH) { + auto m = GetMasks5(state.num_qubits(), qs, cqs, cvals); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); + + unsigned r = 3 + H + cqs.size(); + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; + uint64_t size = uint64_t{1} << n; + + for_.Run(size, f, w, m.imaskh, m.qmaskh, m.cvalsh, idx, state.get()); + } else { + auto m = GetMasks6(state.num_qubits(), qs, cqs, cvals); + FillPermutationIndices(m.qmaskl, idx); + FillControlledMatrixL( + m.cvalsl, m.cmaskl, m.qmaskl, matrix, (fp_type*) w); + + unsigned r = 3 + H + cqs.size() - m.cl; + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; + uint64_t size = uint64_t{1} << n; - for_.Run(size, f, w, ms, xss, idx, rstate); + for_.Run(size, f, w, m.imaskh, m.qmaskh, m.cvalsh, idx, state.get()); + } } - void ApplyGate2LL(const std::vector& qs, - const fp_type* matrix, State& state) const { - unsigned p[8]; - __m256i idx[3]; + template + std::complex ExpectationValueH(const std::vector& qs, + const fp_type* matrix, + const State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, + uint64_t imaskh, uint64_t qmaskh, const fp_type* rstate) { + constexpr unsigned hsize = 1 << H; - auto s = StateSpace::Create(5); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; + __m256 ru, iu, rn, in; + __m256 rs[hsize], is[hsize]; - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); + auto p0 = rstate + _pdep_u64(i, imaskh); - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); + for (unsigned k = 0; k < hsize; ++k) { + uint64_t p = _pdep_u64(k, qmaskh); + + rs[k] = _mm256_load_ps(p0 + p); + is[k] = _mm256_load_ps(p0 + p + 8); } - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } + double re = 0; + double im = 0; + uint64_t j = 0; - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } + for (unsigned k = 0; k < hsize; ++k) { + ru = _mm256_set1_ps(v[j]); + iu = _mm256_set1_ps(v[j + 1]); + rn = _mm256_mul_ps(rs[0], ru); + in = _mm256_mul_ps(rs[0], iu); + rn = _mm256_fnmadd_ps(is[0], iu, rn); + in = _mm256_fmadd_ps(is[0], ru, in); - unsigned l = 2 * (4 * i + m); + j += 2; - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } + for (unsigned l = 1; l < hsize; ++l) { + ru = _mm256_set1_ps(v[j]); + iu = _mm256_set1_ps(v[j + 1]); + rn = _mm256_fmadd_ps(rs[l], ru, rn); + in = _mm256_fmadd_ps(rs[l], iu, in); + rn = _mm256_fnmadd_ps(is[l], iu, rn); + in = _mm256_fmadd_ps(is[l], ru, in); - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; + j += 2; } + + __m256 v_re = _mm256_fmadd_ps(is[k], in, _mm256_mul_ps(rs[k], rn)); + __m256 v_im = _mm256_fnmadd_ps(is[k], rn, _mm256_mul_ps(rs[k], in)); + + re += detail::HorizontalSumAVX(v_re); + im += detail::HorizontalSumAVX(v_im); } - } + return std::complex{re, im}; + }; + + auto m = GetMasks1(qs); + + unsigned k = 3 + H; + unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + uint64_t size = uint64_t{1} << n; + + using Op = std::plus>; + return + for_.RunReduce(size, f, Op(), matrix, m.imaskh, m.qmaskh, state.get()); + } + + template + std::complex ExpectationValueL(const std::vector& qs, + const fp_type* matrix, + const State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const __m256i* idx, fp_type* rstate) { + uint64_t imaskh, uint64_t qmaskh, const __m256i* idx, + const fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; + __m256 rn, in; - __m256 rs[4], is[4]; + __m256 rs[gsize], is[gsize]; - auto p0 = rstate + 16 * i; + auto p0 = rstate + _pdep_u64(i, imaskh); - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm256_load_ps(p0); - is[4 * l] = _mm256_load_ps(p0 + 8); + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); + rs[k2] = _mm256_load_ps(p0 + p); + is[k2] = _mm256_load_ps(p0 + p + 8); + + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm256_permutevar8x32_ps(rs[k2], idx[l - 1]); + is[k2 + l] = _mm256_permutevar8x32_ps(is[k2], idx[l - 1]); } } + double re = 0; + double im = 0; uint64_t j = 0; - for (unsigned l = 0; l < 1; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm256_mul_ps(rs[0], w[j]); in = _mm256_mul_ps(rs[0], w[j + 1]); rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); @@ -728,72 +759,73 @@ class SimulatorAVX final { j += 2; - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm256_fmadd_ps(rs[l], w[j], rn); + in = _mm256_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm256_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm256_fmadd_ps(is[l], w[j], in); j += 2; } - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); + unsigned m = lsize * k; + + __m256 v_re = _mm256_fmadd_ps(is[m], in, _mm256_mul_ps(rs[m], rn)); + __m256 v_im = _mm256_fnmadd_ps(is[m], rn, _mm256_mul_ps(rs[m], in)); + + re += detail::HorizontalSumAVX(v_re); + im += detail::HorizontalSumAVX(v_im); } + + return std::complex{re, im}; }; - fp_type* rstate = state.get(); + __m256i idx[1 << L]; + __m256 w[1 << (1 + 2 * H + L)]; - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + auto m = GetMasks2(qs); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); + + unsigned r = 3 + H; + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, w, idx, rstate); + using Op = std::plus>; + return + for_.RunReduce(size, f, Op(), w, m.imaskh, m.qmaskh, idx, state.get()); } - void ApplyGate3HHH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } +#else // __BMI2__ + template + void ApplyGateH(const std::vector& qs, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { + const uint64_t* ms, const uint64_t* xss, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m256 ru, iu, rn, in; - __m256 rs[8], is[8]; + __m256 rs[hsize], is[hsize]; - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]); + i *= 8; + + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; + } - auto p0 = rstate + 2 * k; + auto p0 = rstate + 2 * ii; - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = _mm256_load_ps(p0 + xss[k]); + is[k] = _mm256_load_ps(p0 + xss[k] + 8); } uint64_t j = 0; - for (unsigned l = 0; l < 8; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm256_set1_ps(v[j]); iu = _mm256_set1_ps(v[j + 1]); rn = _mm256_mul_ps(rs[0], ru); @@ -803,114 +835,71 @@ class SimulatorAVX final { j += 2; - for (unsigned n = 1; n < 8; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm256_set1_ps(v[j]); iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); + rn = _mm256_fmadd_ps(rs[l], ru, rn); + in = _mm256_fmadd_ps(rs[l], iu, in); + rn = _mm256_fnmadd_ps(is[l], iu, rn); + in = _mm256_fmadd_ps(is[l], ru, in); j += 2; } - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); + _mm256_store_ps(p0 + xss[k], rn); + _mm256_store_ps(p0 + xss[k] + 8, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; - unsigned k = 6; + FillIndices(state.num_qubits(), qs, ms, xss); + + unsigned k = 3 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, matrix, ms, xss, rstate); + for_.Run(size, f, matrix, ms, xss, state.get()); } - void ApplyGate3HHL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(8); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } + template + void ApplyGateL(const std::vector& qs, + const fp_type* matrix, State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, + const uint64_t* ms, const uint64_t* xss, const __m256i* idx, + fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; - unsigned l = 2 * (8 * i + m); + __m256 rn, in; + __m256 rs[gsize], is[gsize]; - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } + i *= 8; - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]); + auto p0 = rstate + 2 * ii; - auto p0 = rstate + 2 * k; + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; + rs[k2] = _mm256_load_ps(p0 + xss[k]); + is[k2] = _mm256_load_ps(p0 + xss[k] + 8); - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm256_permutevar8x32_ps(rs[k2], idx[l - 1]); + is[k2 + l] = _mm256_permutevar8x32_ps(is[k2], idx[l - 1]); } } uint64_t j = 0; - for (unsigned l = 0; l < 4; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm256_mul_ps(rs[0], w[j]); in = _mm256_mul_ps(rs[0], w[j + 1]); rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); @@ -918,108 +907,140 @@ class SimulatorAVX final { j += 2; - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm256_fmadd_ps(rs[l], w[j], rn); + in = _mm256_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm256_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm256_fmadd_ps(is[l], w[j], in); j += 2; } - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); + _mm256_store_ps(p0 + xss[k], rn); + _mm256_store_ps(p0 + xss[k] + 8, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + __m256i idx[1 << L]; + __m256 w[1 << (1 + 2 * H + L)]; - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + unsigned qmaskl = GetQMask(qs); + + FillIndices(state.num_qubits(), qs, ms, xss); + FillPermutationIndices(qmaskl, idx); + FillMatrix(qmaskl, matrix, (fp_type*) w); + + unsigned r = 3 + H; + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, w, ms, xss, idx, rstate); + for_.Run(size, f, w, ms, xss, idx, state.get()); } - void ApplyGate3HLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } + template + void ApplyControlledGateHH(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, + const uint64_t* ms, const uint64_t* xss, uint64_t cvalsh, + uint64_t cmaskh, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; - unsigned p[8]; - __m256i idx[3]; + __m256 ru, iu, rn, in; + __m256 rs[hsize], is[hsize]; + + i *= 8; - auto s = StateSpace::Create(7); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; + } - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); + if ((ii & cmaskh) != cvalsh) return; - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); + auto p0 = rstate + 2 * ii; + + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = _mm256_load_ps(p0 + xss[k]); + is[k] = _mm256_load_ps(p0 + xss[k] + 8); } - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } + uint64_t j = 0; - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } + for (unsigned k = 0; k < hsize; ++k) { + ru = _mm256_set1_ps(v[j]); + iu = _mm256_set1_ps(v[j + 1]); + rn = _mm256_mul_ps(rs[0], ru); + in = _mm256_mul_ps(rs[0], iu); + rn = _mm256_fnmadd_ps(is[0], iu, rn); + in = _mm256_fmadd_ps(is[0], ru, in); - unsigned l = 2 * (8 * i + m); + j += 2; - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } + for (unsigned l = 1; l < hsize; ++l) { + ru = _mm256_set1_ps(v[j]); + iu = _mm256_set1_ps(v[j + 1]); + rn = _mm256_fmadd_ps(rs[l], ru, rn); + in = _mm256_fmadd_ps(rs[l], iu, in); + rn = _mm256_fnmadd_ps(is[l], iu, rn); + in = _mm256_fmadd_ps(is[l], ru, in); - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; + j += 2; } + + _mm256_store_ps(p0 + xss[k], rn); + _mm256_store_ps(p0 + xss[k] + 8, in); } - } + }; + + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + + auto m = GetMasks7(state.num_qubits(), qs, cqs, cvals); + FillIndices(state.num_qubits(), qs, ms, xss); + + unsigned k = 3 + H; + unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + uint64_t size = uint64_t{1} << n; + for_.Run(size, f, matrix, ms, xss, m.cvalsh, m.cmaskh, state.get()); + } + + template + void ApplyControlledGateHL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, fp_type* rstate) { + const uint64_t* ms, const uint64_t* xss, uint64_t cvalsh, + uint64_t cmaskh, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m256 rn, in; - __m256 rs[8], is[8]; + __m256 rs[hsize], is[hsize]; + + i *= 8; - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]); + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; + } - auto p0 = rstate + 2 * k; + if ((ii & cmaskh) != cvalsh) return; - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); + auto p0 = rstate + 2 * ii; - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = _mm256_load_ps(p0 + xss[k]); + is[k] = _mm256_load_ps(p0 + xss[k] + 8); } uint64_t j = 0; - for (unsigned l = 0; l < 2; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm256_mul_ps(rs[0], w[j]); in = _mm256_mul_ps(rs[0], w[j + 1]); rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); @@ -1027,87 +1048,76 @@ class SimulatorAVX final { j += 2; - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < hsize; ++l) { + rn = _mm256_fmadd_ps(rs[l], w[j], rn); + in = _mm256_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm256_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm256_fmadd_ps(is[l], w[j], in); j += 2; } - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); + _mm256_store_ps(p0 + xss[k], rn); + _mm256_store_ps(p0 + xss[k] + 8, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + __m256 w[1 << (1 + 2 * H)]; - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + auto m = GetMasks8<3>(state.num_qubits(), qs, cqs, cvals); + FillIndices(state.num_qubits(), qs, ms, xss); + FillControlledMatrixH(m.cvalsl, m.cmaskl, matrix, (fp_type*) w); + + unsigned r = 3 + H; + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, w, ms, xss, idx, rstate); + for_.Run(size, f, w, ms, xss, m.cvalsh, m.cmaskh, state.get()); } - void ApplyGate3LLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - unsigned p[8]; - __m256i idx[7]; + template + void ApplyControlledGateL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, + const uint64_t* ms, const uint64_t* xss, uint64_t cvalsh, + uint64_t cmaskh, const __m256i* idx, fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; - auto s = StateSpace::Create(6); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; + __m256 rn, in; + __m256 rs[gsize], is[gsize]; - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); + i *= 8; - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; } - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 8 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } + if ((ii & cmaskh) != cvalsh) return; - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; + auto p0 = rstate + 2 * ii; - auto p0 = rstate + 16 * i; + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; - for (unsigned l = 0; l < 1; ++l) { - rs[8 * l] = _mm256_load_ps(p0); - is[8 * l] = _mm256_load_ps(p0 + 8); + rs[k2] = _mm256_load_ps(p0 + xss[k]); + is[k2] = _mm256_load_ps(p0 + xss[k] + 8); - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm256_permutevar8x32_ps(rs[k2], idx[l - 1]); + is[k2 + l] = _mm256_permutevar8x32_ps(is[k2], idx[l - 1]); } } uint64_t j = 0; - for (unsigned l = 0; l < 1; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm256_mul_ps(rs[0], w[j]); in = _mm256_mul_ps(rs[0], w[j + 1]); rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); @@ -1115,72 +1125,79 @@ class SimulatorAVX final { j += 2; - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm256_fmadd_ps(rs[l], w[j], rn); + in = _mm256_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm256_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm256_fmadd_ps(is[l], w[j], in); j += 2; } - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); + _mm256_store_ps(p0 + xss[k], rn); + _mm256_store_ps(p0 + xss[k] + 8, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + __m256i idx[1 << L]; + __m256 w[1 << (1 + 2 * H + L)]; - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + FillIndices(state.num_qubits(), qs, ms, xss); + + unsigned r = 3 + H; + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, w, idx, rstate); - } + if (CH) { + auto m = GetMasks9(state.num_qubits(), qs, cqs, cvals); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); - void ApplyGate4HHHH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; + for_.Run(size, f, w, ms, xss, m.cvalsh, m.cmaskh, idx, state.get()); + } else { + auto m = GetMasks10(state.num_qubits(), qs, cqs, cvals); + FillPermutationIndices(m.qmaskl, idx); + FillControlledMatrixL( + m.cvalsl, m.cmaskl, m.qmaskl, matrix, (fp_type*) w); - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; + for_.Run(size, f, w, ms, xss, m.cvalsh, m.cmaskh, idx, state.get()); } + } + template + std::complex ExpectationValueH(const std::vector& qs, + const fp_type* matrix, + const State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { + const fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m256 ru, iu, rn, in; - __m256 rs[16], is[16]; + __m256 rs[hsize], is[hsize]; + + i *= 8; - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]) | (128 * i & ms[4]); + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; + } - auto p0 = rstate + 2 * k; + auto p0 = rstate + 2 * ii; - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = _mm256_load_ps(p0 + xss[k]); + is[k] = _mm256_load_ps(p0 + xss[k] + 8); } + double re = 0; + double im = 0; uint64_t j = 0; - for (unsigned l = 0; l < 16; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm256_set1_ps(v[j]); iu = _mm256_set1_ps(v[j + 1]); rn = _mm256_mul_ps(rs[0], ru); @@ -1190,6379 +1207,81 @@ class SimulatorAVX final { j += 2; - for (unsigned n = 1; n < 16; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm256_set1_ps(v[j]); iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); + rn = _mm256_fmadd_ps(rs[l], ru, rn); + in = _mm256_fmadd_ps(rs[l], iu, in); + rn = _mm256_fnmadd_ps(is[l], iu, rn); + in = _mm256_fmadd_ps(is[l], ru, in); j += 2; } - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); + __m256 v_re = _mm256_fmadd_ps(is[k], in, _mm256_mul_ps(rs[k], rn)); + __m256 v_im = _mm256_fnmadd_ps(is[k], rn, _mm256_mul_ps(rs[k], in)); + + re += detail::HorizontalSumAVX(v_re); + im += detail::HorizontalSumAVX(v_im); } + + return std::complex{re, im}; }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; - unsigned k = 7; + FillIndices(state.num_qubits(), qs, ms, xss); + + unsigned k = 3 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, matrix, ms, xss, rstate); + using Op = std::plus>; + return for_.RunReduce(size, f, Op(), matrix, ms, xss, state.get()); } - void ApplyGate4HHHL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(10); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate4HHLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[3]; - - auto s = StateSpace::Create(9); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate4HLLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[7]; - - auto s = StateSpace::Create(8); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (128 * i + 16 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[8 * l] = _mm256_load_ps(p0 + xss[l]); - is[8 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate5HHHHH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[32], is[32]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]) | (128 * i & ms[4]) | (256 * i & ms[5]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 32; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, rstate); - } - - void ApplyGate5HHHHL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(12); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 32 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[32], is[32]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]) | (128 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate5HHHLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[3]; - - auto s = StateSpace::Create(11); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (128 * i + 32 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[32], is[32]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate5HHLLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 3] + 1); - ms[i] = ((uint64_t{1} << qs[i + 3]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[7]; - - auto s = StateSpace::Create(10); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (256 * i + 32 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[32], is[32]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[8 * l] = _mm256_load_ps(p0 + xss[l]); - is[8 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate6HHHHHH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[6]; - uint64_t ms[7]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 6; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[6] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[5] - 1); - - uint64_t xss[64]; - for (unsigned i = 0; i < 64; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 6; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[64], is[64]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]) | (128 * i & ms[4]) | (256 * i & ms[5]) - | (512 * i & ms[6]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 64; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 64; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 9; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, rstate); - } - - void ApplyGate6HHHHHL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(14); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 32; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (128 * i + 64 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[64], is[64]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]) | (128 * i & ms[4]) | (256 * i & ms[5]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 32; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate6HHHHLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[3]; - - auto s = StateSpace::Create(13); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (256 * i + 64 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[64], is[64]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]) | (128 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate6HHHLLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 3] + 1); - ms[i] = ((uint64_t{1} << qs[i + 3]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[7]; - - auto s = StateSpace::Create(12); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (512 * i + 64 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[64], is[64]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[8 * l] = _mm256_load_ps(p0 + xss[l]); - is[8 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyControlledGate1H_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[2], is[2]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate1H_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - - auto s = StateSpace::Create(5); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (2 * i + 2 * k + m); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 2 == (p[j] / 2) % 2 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[2], is[2]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate1L_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(4); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[2], is[2]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm256_load_ps(p0); - is[2 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate1L_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(4); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 2 == (p[j] / 2) % 2 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[2], is[2]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm256_load_ps(p0); - is[2 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate2HH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate2HH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - - auto s = StateSpace::Create(7); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (4 * i + 4 * k + m); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate2HL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(6); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate2HL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(6); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate2LL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[3]; - - auto s = StateSpace::Create(5); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm256_load_ps(p0); - is[4 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate2LL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[3]; - - auto s = StateSpace::Create(5); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm256_load_ps(p0); - is[4 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate3HHH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate3HHH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - - auto s = StateSpace::Create(9); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (8 * i + 8 * k + m); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate3HHL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(8); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate3HHL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(8); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate3HLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[3]; - - auto s = StateSpace::Create(7); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate3HLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[3]; - - auto s = StateSpace::Create(7); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate3LLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[7]; - - auto s = StateSpace::Create(6); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 8 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[8 * l] = _mm256_load_ps(p0); - is[8 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate3LLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[7]; - - auto s = StateSpace::Create(6); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 8 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[8 * l] = _mm256_load_ps(p0); - is[8 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate4HHHH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate4HHHH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - - auto s = StateSpace::Create(11); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 16 * k + m); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate4HHHL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(10); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate4HHHL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(10); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate4HHLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[3]; - - auto s = StateSpace::Create(9); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate4HHLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[3]; - - auto s = StateSpace::Create(9); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate4HLLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[7]; - - auto s = StateSpace::Create(8); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (128 * i + 16 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[8 * l] = _mm256_load_ps(p0 + xss[l]); - is[8 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate4HLLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[7]; - - auto s = StateSpace::Create(8); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (128 * i + 16 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[8 * l] = _mm256_load_ps(p0 + xss[l]); - is[8 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - std::complex ExpectationValue1H(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[2], is[2]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - __m256 v_re = _mm256_fmadd_ps(is[l], in, _mm256_mul_ps(rs[l], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[l], rn, _mm256_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue1L(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(4); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[2], is[2]; - - auto p0 = rstate + 16 * i; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm256_load_ps(p0); - is[2 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - __m256 v_re = _mm256_fmadd_ps(is[l], in, _mm256_mul_ps(rs[l], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[l], rn, _mm256_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, idx, rstate); - } - - std::complex ExpectationValue2HH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[4], is[4]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - __m256 v_re = _mm256_fmadd_ps(is[l], in, _mm256_mul_ps(rs[l], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[l], rn, _mm256_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue2HL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(6); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[4], is[4]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 2 * l; - - __m256 v_re = _mm256_fmadd_ps(is[m], in, _mm256_mul_ps(rs[m], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[m], rn, _mm256_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue2LL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - unsigned p[8]; - __m256i idx[3]; - - auto s = StateSpace::Create(5); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[4], is[4]; - - auto p0 = rstate + 16 * i; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm256_load_ps(p0); - is[4 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - __m256 v_re = _mm256_fmadd_ps(is[l], in, _mm256_mul_ps(rs[l], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[l], rn, _mm256_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, idx, rstate); - } - - std::complex ExpectationValue3HHH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[8], is[8]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - __m256 v_re = _mm256_fmadd_ps(is[l], in, _mm256_mul_ps(rs[l], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[l], rn, _mm256_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue3HHL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(8); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 2 * l; - - __m256 v_re = _mm256_fmadd_ps(is[m], in, _mm256_mul_ps(rs[m], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[m], rn, _mm256_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue3HLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[3]; - - auto s = StateSpace::Create(7); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 4 * l; - - __m256 v_re = _mm256_fmadd_ps(is[m], in, _mm256_mul_ps(rs[m], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[m], rn, _mm256_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue3LLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - unsigned p[8]; - __m256i idx[7]; - - auto s = StateSpace::Create(6); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 8 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - auto p0 = rstate + 16 * i; - - for (unsigned l = 0; l < 1; ++l) { - rs[8 * l] = _mm256_load_ps(p0); - is[8 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - __m256 v_re = _mm256_fmadd_ps(is[l], in, _mm256_mul_ps(rs[l], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[l], rn, _mm256_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, idx, rstate); - } - - std::complex ExpectationValue4HHHH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[16], is[16]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]) | (128 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - __m256 v_re = _mm256_fmadd_ps(is[l], in, _mm256_mul_ps(rs[l], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[l], rn, _mm256_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue4HHHL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(10); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 2 * l; - - __m256 v_re = _mm256_fmadd_ps(is[m], in, _mm256_mul_ps(rs[m], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[m], rn, _mm256_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue4HHLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[3]; - - auto s = StateSpace::Create(9); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 4 * l; - - __m256 v_re = _mm256_fmadd_ps(is[m], in, _mm256_mul_ps(rs[m], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[m], rn, _mm256_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue4HLLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[7]; - - auto s = StateSpace::Create(8); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (128 * i + 16 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[8 * l] = _mm256_load_ps(p0 + xss[l]); - is[8 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 8 * l; - - __m256 v_re = _mm256_fmadd_ps(is[m], in, _mm256_mul_ps(rs[m], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[m], rn, _mm256_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue5HHHHH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[32], is[32]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]) | (128 * i & ms[4]) | (256 * i & ms[5]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 32; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - __m256 v_re = _mm256_fmadd_ps(is[l], in, _mm256_mul_ps(rs[l], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[l], rn, _mm256_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue5HHHHL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(12); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 32 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[32], is[32]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]) | (128 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 2 * l; - - __m256 v_re = _mm256_fmadd_ps(is[m], in, _mm256_mul_ps(rs[m], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[m], rn, _mm256_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue5HHHLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[3]; - - auto s = StateSpace::Create(11); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (128 * i + 32 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[32], is[32]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 4 * l; - - __m256 v_re = _mm256_fmadd_ps(is[m], in, _mm256_mul_ps(rs[m], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[m], rn, _mm256_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue5HHLLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 3] + 1); - ms[i] = ((uint64_t{1} << qs[i + 3]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[7]; - - auto s = StateSpace::Create(10); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (256 * i + 32 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - + template + std::complex ExpectationValueL(const std::vector& qs, + const fp_type* matrix, + const State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[32], is[32]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[8 * l] = _mm256_load_ps(p0 + xss[l]); - is[8 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 8 * l; - - __m256 v_re = _mm256_fmadd_ps(is[m], in, _mm256_mul_ps(rs[m], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[m], rn, _mm256_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue6HHHHHH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[6]; - uint64_t ms[7]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 6; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[6] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[5] - 1); - - uint64_t xss[64]; - for (unsigned i = 0; i < 64; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 6; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, + const uint64_t* ms, const uint64_t* xss, const __m256i* idx, const fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[64], is[64]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]) | (128 * i & ms[4]) | (256 * i & ms[5]) - | (512 * i & ms[6]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 64; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 64; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - __m256 v_re = _mm256_fmadd_ps(is[l], in, _mm256_mul_ps(rs[l], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[l], rn, _mm256_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 9; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue6HHHHHL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[1]; - - auto s = StateSpace::Create(14); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 32; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (128 * i + 64 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (64 * i + m); + __m256 rn, in; + __m256 rs[gsize], is[gsize]; - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } + i *= 8; - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[64], is[64]; - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]) | (128 * i & ms[4]) | (256 * i & ms[5]); + auto p0 = rstate + 2 * ii; - auto p0 = rstate + 2 * k; + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; - for (unsigned l = 0; l < 32; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); + rs[k2] = _mm256_load_ps(p0 + xss[k]); + is[k2] = _mm256_load_ps(p0 + xss[k] + 8); - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm256_permutevar8x32_ps(rs[k2], idx[l - 1]); + is[k2 + l] = _mm256_permutevar8x32_ps(is[k2], idx[l - 1]); } } double re = 0; double im = 0; - uint64_t j = 0; - for (unsigned l = 0; l < 32; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm256_mul_ps(rs[0], w[j]); in = _mm256_mul_ps(rs[0], w[j + 1]); rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); @@ -7570,16 +1289,16 @@ class SimulatorAVX final { j += 2; - for (unsigned n = 1; n < 64; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm256_fmadd_ps(rs[l], w[j], rn); + in = _mm256_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm256_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm256_fmadd_ps(is[l], w[j], in); j += 2; } - unsigned m = 2 * l; + unsigned m = lsize * k; __m256 v_re = _mm256_fmadd_ps(is[m], in, _mm256_mul_ps(rs[m], rn)); __m256 v_im = _mm256_fnmadd_ps(is[m], rn, _mm256_mul_ps(rs[m], in)); @@ -7591,272 +1310,40 @@ class SimulatorAVX final { return std::complex{re, im}; }; - const fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue6HHHHLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[3]; - - auto s = StateSpace::Create(13); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (256 * i + 64 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[64], is[64]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]) | (128 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 4 * l; - - __m256 v_re = _mm256_fmadd_ps(is[m], in, _mm256_mul_ps(rs[m], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[m], rn, _mm256_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + __m256i idx[1 << L]; + __m256 w[1 << (1 + 2 * H + L)]; - return std::complex{re, im}; - }; + unsigned qmaskl = GetQMask(qs); - const fp_type* rstate = state.get(); + FillIndices(state.num_qubits(), qs, ms, xss); + FillPermutationIndices(qmaskl, idx); + FillMatrix(qmaskl, matrix, (fp_type*) w); - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + unsigned r = 3 + H; + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; uint64_t size = uint64_t{1} << n; using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); + return for_.RunReduce(size, f, Op(), w, ms, xss, idx, state.get()); } - std::complex ExpectationValue6HHHLLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 3] + 1); - ms[i] = ((uint64_t{1} << qs[i + 3]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[7]; +#endif // __BMI2__ - auto s = StateSpace::Create(12); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; + template + static void FillPermutationIndices(unsigned qmaskl, __m256i* idx) { + constexpr unsigned lsize = 1 << L; - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); + for (unsigned i = 0; i < lsize - 1; ++i) { + unsigned p[8]; - for (unsigned i = 0; i < 7; ++i) { for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); + p[j] = MaskedAdd<3>(j, i + 1, qmaskl, lsize) | (j & (-1 ^ qmaskl)); } idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (512 * i + 64 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, const fp_type* rstate) { - __m256 rn, in; - __m256 rs[64], is[64]; - - uint64_t k = (8 * i & ms[0]) | (16 * i & ms[1]) | (32 * i & ms[2]) - | (64 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[8 * l] = _mm256_load_ps(p0 + xss[l]); - is[8 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 8 * l; - - __m256 v_re = _mm256_fmadd_ps(is[m], in, _mm256_mul_ps(rs[m], rn)); - __m256 v_im = _mm256_fnmadd_ps(is[m], rn, _mm256_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX(v_re); - im += detail::HorizontalSumAVX(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - static unsigned MaskedAdd( - unsigned a, unsigned b, unsigned mask, unsigned lsize) { - unsigned c = bits::CompressBits(a, 3, mask); - return bits::ExpandBits((c + b) % lsize, 3, mask); } For for_; diff --git a/lib/simulator_avx512.h b/lib/simulator_avx512.h index ddce44db..9e03a867 100644 --- a/lib/simulator_avx512.h +++ b/lib/simulator_avx512.h @@ -17,11 +17,12 @@ #include -#include #include #include +#include +#include -#include "bits.h" +#include "simulator.h" #include "statespace_avx512.h" namespace qsim { @@ -30,7 +31,7 @@ namespace qsim { * Quantum circuit simulator with AVX512 vectorization. */ template -class SimulatorAVX512 final { +class SimulatorAVX512 final : public SimulatorBase { public: using StateSpace = StateSpaceAVX512; using State = typename StateSpace::State; @@ -52,68 +53,68 @@ class SimulatorAVX512 final { switch (qs.size()) { case 1: if (qs[0] > 3) { - ApplyGate1H(qs, matrix, state); + ApplyGateH<1>(qs, matrix, state); } else { - ApplyGate1L(qs, matrix, state); + ApplyGateL<0, 1>(qs, matrix, state); } break; case 2: if (qs[0] > 3) { - ApplyGate2HH(qs, matrix, state); + ApplyGateH<2>(qs, matrix, state); } else if (qs[1] > 3) { - ApplyGate2HL(qs, matrix, state); + ApplyGateL<1, 1>(qs, matrix, state); } else { - ApplyGate2LL(qs, matrix, state); + ApplyGateL<0, 2>(qs, matrix, state); } break; case 3: if (qs[0] > 3) { - ApplyGate3HHH(qs, matrix, state); + ApplyGateH<3>(qs, matrix, state); } else if (qs[1] > 3) { - ApplyGate3HHL(qs, matrix, state); + ApplyGateL<2, 1>(qs, matrix, state); } else if (qs[2] > 3) { - ApplyGate3HLL(qs, matrix, state); + ApplyGateL<1, 2>(qs, matrix, state); } else { - ApplyGate3LLL(qs, matrix, state); + ApplyGateL<0, 3>(qs, matrix, state); } break; case 4: if (qs[0] > 3) { - ApplyGate4HHHH(qs, matrix, state); + ApplyGateH<4>(qs, matrix, state); } else if (qs[1] > 3) { - ApplyGate4HHHL(qs, matrix, state); + ApplyGateL<3, 1>(qs, matrix, state); } else if (qs[2] > 3) { - ApplyGate4HHLL(qs, matrix, state); + ApplyGateL<2, 2>(qs, matrix, state); } else if (qs[3] > 3) { - ApplyGate4HLLL(qs, matrix, state); + ApplyGateL<1, 3>(qs, matrix, state); } else { - ApplyGate4LLLL(qs, matrix, state); + ApplyGateL<0, 4>(qs, matrix, state); } break; case 5: if (qs[0] > 3) { - ApplyGate5HHHHH(qs, matrix, state); + ApplyGateH<5>(qs, matrix, state); } else if (qs[1] > 3) { - ApplyGate5HHHHL(qs, matrix, state); + ApplyGateL<4, 1>(qs, matrix, state); } else if (qs[2] > 3) { - ApplyGate5HHHLL(qs, matrix, state); + ApplyGateL<3, 2>(qs, matrix, state); } else if (qs[3] > 3) { - ApplyGate5HHLLL(qs, matrix, state); + ApplyGateL<2, 3>(qs, matrix, state); } else { - ApplyGate5HLLLL(qs, matrix, state); + ApplyGateL<1, 4>(qs, matrix, state); } break; case 6: if (qs[0] > 3) { - ApplyGate6HHHHHH(qs, matrix, state); + ApplyGateH<6>(qs, matrix, state); } else if (qs[1] > 3) { - ApplyGate6HHHHHL(qs, matrix, state); + ApplyGateL<5, 1>(qs, matrix, state); } else if (qs[2] > 3) { - ApplyGate6HHHHLL(qs, matrix, state); + ApplyGateL<4, 2>(qs, matrix, state); } else if (qs[3] > 3) { - ApplyGate6HHHLLL(qs, matrix, state); + ApplyGateL<3, 3>(qs, matrix, state); } else { - ApplyGate6HHLLLL(qs, matrix, state); + ApplyGateL<2, 4>(qs, matrix, state); } break; default: @@ -126,13 +127,16 @@ class SimulatorAVX512 final { * Applies a controlled gate using AVX512 instructions. * @param qs Indices of the qubits affected by this gate. * @param cqs Indices of control qubits. - * @param cmask Bit mask of control qubit values. + * @param cvals Bit mask of control qubit values. * @param matrix Matrix representation of the gate to be applied. * @param state The state of the system, to be updated by this method. */ void ApplyControlledGate(const std::vector& qs, - const std::vector& cqs, uint64_t cmask, + const std::vector& cqs, uint64_t cvals, const fp_type* matrix, State& state) const { + // Assume qs[0] < qs[1] < qs[2] < ... . + // Assume cqs[0] < cqs[1] < cqs[2] < ... . + if (cqs.size() == 0) { ApplyGate(qs, matrix, state); return; @@ -142,96 +146,96 @@ class SimulatorAVX512 final { case 1: if (qs[0] > 3) { if (cqs[0] > 3) { - ApplyControlledGate1H_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate1H_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<1>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 3) { - ApplyControlledGate1L_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate1L_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 1, 0>(qs, cqs, cvals, matrix, state); } } break; case 2: if (qs[0] > 3) { if (cqs[0] > 3) { - ApplyControlledGate2HH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<2>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2HH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<2>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 3) { if (cqs[0] > 3) { - ApplyControlledGate2HL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2HL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 1, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 3) { - ApplyControlledGate2LL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2LL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 2, 0>(qs, cqs, cvals, matrix, state); } } break; case 3: if (qs[0] > 3) { if (cqs[0] > 3) { - ApplyControlledGate3HHH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<3>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HHH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<3>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 3) { if (cqs[0] > 3) { - ApplyControlledGate3HHL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HHL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 1, 0>(qs, cqs, cvals, matrix, state); } } else if (qs[2] > 3) { if (cqs[0] > 3) { - ApplyControlledGate3HLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 2, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 3) { - ApplyControlledGate3LLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 3, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3LLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 3, 0>(qs, cqs, cvals, matrix, state); } } break; case 4: if (qs[0] > 3) { if (cqs[0] > 3) { - ApplyControlledGate4HHHH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<4>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHHH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<4>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 3) { if (cqs[0] > 3) { - ApplyControlledGate4HHHL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<3, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHHL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<3, 1, 0>(qs, cqs, cvals, matrix, state); } } else if (qs[2] > 3) { if (cqs[0] > 3) { - ApplyControlledGate4HHLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 2, 0>(qs, cqs, cvals, matrix, state); } } else if (qs[3] > 3) { if (cqs[0] > 3) { - ApplyControlledGate4HLLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 3, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HLLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 3, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 3) { - ApplyControlledGate4LLLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 4, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4LLLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 4, 0>(qs, cqs, cvals, matrix, state); } } break; @@ -256,68 +260,68 @@ class SimulatorAVX512 final { switch (qs.size()) { case 1: if (qs[0] > 3) { - return ExpectationValue1H(qs, matrix, state); + return ExpectationValueH<1>(qs, matrix, state); } else { - return ExpectationValue1L(qs, matrix, state); + return ExpectationValueL<0, 1>(qs, matrix, state); } break; case 2: if (qs[0] > 3) { - return ExpectationValue2HH(qs, matrix, state); + return ExpectationValueH<2>(qs, matrix, state); } else if (qs[1] > 3) { - return ExpectationValue2HL(qs, matrix, state); + return ExpectationValueL<1, 1>(qs, matrix, state); } else { - return ExpectationValue2LL(qs, matrix, state); + return ExpectationValueL<0, 2>(qs, matrix, state); } break; case 3: if (qs[0] > 3) { - return ExpectationValue3HHH(qs, matrix, state); + return ExpectationValueH<3>(qs, matrix, state); } else if (qs[1] > 3) { - return ExpectationValue3HHL(qs, matrix, state); + return ExpectationValueL<2, 1>(qs, matrix, state); } else if (qs[2] > 3) { - return ExpectationValue3HLL(qs, matrix, state); + return ExpectationValueL<1, 2>(qs, matrix, state); } else { - return ExpectationValue3LLL(qs, matrix, state); + return ExpectationValueL<0, 3>(qs, matrix, state); } break; case 4: if (qs[0] > 3) { - return ExpectationValue4HHHH(qs, matrix, state); + return ExpectationValueH<4>(qs, matrix, state); } else if (qs[1] > 3) { - return ExpectationValue4HHHL(qs, matrix, state); + return ExpectationValueL<3, 1>(qs, matrix, state); } else if (qs[2] > 3) { - return ExpectationValue4HHLL(qs, matrix, state); + return ExpectationValueL<2, 2>(qs, matrix, state); } else if (qs[3] > 3) { - return ExpectationValue4HLLL(qs, matrix, state); + return ExpectationValueL<1, 3>(qs, matrix, state); } else { - return ExpectationValue4LLLL(qs, matrix, state); + return ExpectationValueL<0, 4>(qs, matrix, state); } break; case 5: if (qs[0] > 3) { - return ExpectationValue5HHHHH(qs, matrix, state); + return ExpectationValueH<5>(qs, matrix, state); } else if (qs[1] > 3) { - return ExpectationValue5HHHHL(qs, matrix, state); + return ExpectationValueL<4, 1>(qs, matrix, state); } else if (qs[2] > 3) { - return ExpectationValue5HHHLL(qs, matrix, state); + return ExpectationValueL<3, 2>(qs, matrix, state); } else if (qs[3] > 3) { - return ExpectationValue5HHLLL(qs, matrix, state); + return ExpectationValueL<2, 3>(qs, matrix, state); } else { - return ExpectationValue5HLLLL(qs, matrix, state); + return ExpectationValueL<1, 4>(qs, matrix, state); } break; case 6: if (qs[0] > 3) { - return ExpectationValue6HHHHHH(qs, matrix, state); + return ExpectationValueH<6>(qs, matrix, state); } else if (qs[1] > 3) { - return ExpectationValue6HHHHHL(qs, matrix, state); + return ExpectationValueL<5, 1>(qs, matrix, state); } else if (qs[2] > 3) { - return ExpectationValue6HHHHLL(qs, matrix, state); + return ExpectationValueL<4, 2>(qs, matrix, state); } else if (qs[3] > 3) { - return ExpectationValue6HHHLLL(qs, matrix, state); + return ExpectationValueL<3, 3>(qs, matrix, state); } else { - return ExpectationValue6HHLLLL(qs, matrix, state); + return ExpectationValueL<2, 4>(qs, matrix, state); } break; default: @@ -336,44 +340,28 @@ class SimulatorAVX512 final { } private: - void ApplyGate1H(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - + template + void ApplyGateH(const std::vector& qs, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { + uint64_t imaskh, uint64_t qmaskh, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m512 ru, iu, rn, in; - __m512 rs[2], is[2]; + __m512 rs[hsize], is[hsize]; - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]); + auto p0 = rstate + _pdep_u64(i, imaskh); - auto p0 = rstate + 2 * k; + for (unsigned k = 0; k < hsize; ++k) { + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); + rs[k] = _mm512_load_ps(p0 + p); + is[k] = _mm512_load_ps(p0 + p + 16); } uint64_t j = 0; - for (unsigned l = 0; l < 2; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm512_set1_ps(v[j]); iu = _mm512_set1_ps(v[j + 1]); rn = _mm512_mul_ps(rs[0], ru); @@ -383,91 +371,64 @@ class SimulatorAVX512 final { j += 2; - for (unsigned n = 1; n < 2; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm512_set1_ps(v[j]); iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); + rn = _mm512_fmadd_ps(rs[l], ru, rn); + in = _mm512_fmadd_ps(rs[l], iu, in); + rn = _mm512_fnmadd_ps(is[l], iu, rn); + in = _mm512_fmadd_ps(is[l], ru, in); j += 2; } - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm512_store_ps(p0 + p, rn); + _mm512_store_ps(p0 + p + 16, in); } }; - fp_type* rstate = state.get(); + auto m = GetMasks1(qs); - unsigned k = 5; + unsigned k = 4 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, matrix, ms, xss, rstate); + for_.Run(size, f, matrix, m.imaskh, m.qmaskh, state.get()); } - void ApplyGate1L(const std::vector& qs, - const fp_type* matrix, State& state) const { - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - + template + void ApplyGateL(const std::vector& qs, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const __m512i* idx, fp_type* rstate) { + uint64_t imaskh, uint64_t qmaskh, const __m512i* idx, + fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; + __m512 rn, in; - __m512 rs[2], is[2]; + __m512 rs[gsize], is[gsize]; - auto p0 = rstate + 32 * i; + auto p0 = rstate + _pdep_u64(i, imaskh); - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm512_load_ps(p0); - is[2 * l] = _mm512_load_ps(p0 + 16); + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); + rs[k2] = _mm512_load_ps(p0 + p); + is[k2] = _mm512_load_ps(p0 + p + 16); + + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm512_permutexvar_ps(idx[l - 1], rs[k2]); + is[k2 + l] = _mm512_permutexvar_ps(idx[l - 1], is[k2]); } } uint64_t j = 0; - for (unsigned l = 0; l < 1; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm512_mul_ps(rs[0], w[j]); in = _mm512_mul_ps(rs[0], w[j + 1]); rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); @@ -475,71 +436,60 @@ class SimulatorAVX512 final { j += 2; - for (unsigned n = 1; n < 2; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm512_fmadd_ps(rs[l], w[j], rn); + in = _mm512_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm512_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm512_fmadd_ps(is[l], w[j], in); j += 2; } - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm512_store_ps(p0 + p, rn); + _mm512_store_ps(p0 + p + 16, in); } }; - fp_type* rstate = state.get(); + __m512i idx[1 << L]; + __m512 w[1 << (1 + 2 * H + L)]; - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + auto m = GetMasks2(qs); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); + + unsigned r = 4 + H; + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, w, idx, rstate); + for_.Run(size, f, w, m.imaskh, m.qmaskh, idx, state.get()); } - void ApplyGate2HH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - + template + void ApplyControlledGateHH(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, + uint64_t imaskh, uint64_t qmaskh, uint64_t cvalsh, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m512 ru, iu, rn, in; - __m512 rs[4], is[4]; + __m512 rs[hsize], is[hsize]; - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]); + auto p0 = rstate + (_pdep_u64(i, imaskh) | cvalsh); - auto p0 = rstate + 2 * k; + for (unsigned k = 0; k < hsize; ++k) { + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); + rs[k] = _mm512_load_ps(p0 + p); + is[k] = _mm512_load_ps(p0 + p + 16); } uint64_t j = 0; - for (unsigned l = 0; l < 4; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm512_set1_ps(v[j]); iu = _mm512_set1_ps(v[j + 1]); rn = _mm512_mul_ps(rs[0], ru); @@ -549,112 +499,57 @@ class SimulatorAVX512 final { j += 2; - for (unsigned n = 1; n < 4; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm512_set1_ps(v[j]); iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); + rn = _mm512_fmadd_ps(rs[l], ru, rn); + in = _mm512_fmadd_ps(rs[l], iu, in); + rn = _mm512_fnmadd_ps(is[l], iu, rn); + in = _mm512_fmadd_ps(is[l], ru, in); j += 2; } - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm512_store_ps(p0 + p, rn); + _mm512_store_ps(p0 + p + 16, in); } }; - fp_type* rstate = state.get(); + auto m = GetMasks3(state.num_qubits(), qs, cqs, cvals); - unsigned k = 6; + unsigned k = 4 + H + cqs.size(); unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, matrix, ms, xss, rstate); + for_.Run(size, f, matrix, m.imaskh, m.qmaskh, m.cvalsh, state.get()); } - void ApplyGate2HL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(7); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - + template + void ApplyControlledGateHL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[4], is[4]; + uint64_t imaskh, uint64_t qmaskh, uint64_t cvalsh, + fp_type* rstate) { + constexpr unsigned hsize = 1 << H; - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]); + __m512 rn, in; + __m512 rs[hsize], is[hsize]; - auto p0 = rstate + 2 * k; + auto p0 = rstate + (_pdep_u64(i, imaskh) | cvalsh); - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); + for (unsigned k = 0; k < hsize; ++k) { + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } + rs[k] = _mm512_load_ps(p0 + p); + is[k] = _mm512_load_ps(p0 + p + 16); } uint64_t j = 0; - for (unsigned l = 0; l < 2; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm512_mul_ps(rs[0], w[j]); in = _mm512_mul_ps(rs[0], w[j + 1]); rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); @@ -662,89 +557,66 @@ class SimulatorAVX512 final { j += 2; - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < hsize; ++l) { + rn = _mm512_fmadd_ps(rs[l], w[j], rn); + in = _mm512_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm512_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm512_fmadd_ps(is[l], w[j], in); j += 2; } - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm512_store_ps(p0 + p, rn); + _mm512_store_ps(p0 + p + 16, in); } }; - fp_type* rstate = state.get(); + __m512 w[1 << (1 + 2 * H)]; - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + auto m = GetMasks4(state.num_qubits(), qs, cqs, cvals); + FillControlledMatrixH(m.cvalsl, m.cmaskl, matrix, (fp_type*) w); + + unsigned r = 4 + H + cqs.size() - m.cl; + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, w, ms, xss, idx, rstate); + for_.Run(size, f, w, m.imaskh, m.qmaskh, m.cvalsh, state.get()); } - void ApplyGate2LL(const std::vector& qs, - const fp_type* matrix, State& state) const { - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(6); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - + template + void ApplyControlledGateL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, + uint64_t imaskh, uint64_t qmaskh, uint64_t cvalsh, const __m512i* idx, fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; + __m512 rn, in; - __m512 rs[4], is[4]; + __m512 rs[gsize], is[gsize]; + + auto p0 = rstate + (_pdep_u64(i, imaskh) | cvalsh); - auto p0 = rstate + 32 * i; + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm512_load_ps(p0); - is[4 * l] = _mm512_load_ps(p0 + 16); + rs[k2] = _mm512_load_ps(p0 + p); + is[k2] = _mm512_load_ps(p0 + p + 16); - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm512_permutexvar_ps(idx[l - 1], rs[k2]); + is[k2 + l] = _mm512_permutexvar_ps(idx[l - 1], is[k2]); } } uint64_t j = 0; - for (unsigned l = 0; l < 1; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm512_mul_ps(rs[0], w[j]); in = _mm512_mul_ps(rs[0], w[j + 1]); rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); @@ -752,72 +624,74 @@ class SimulatorAVX512 final { j += 2; - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm512_fmadd_ps(rs[l], w[j], rn); + in = _mm512_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm512_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm512_fmadd_ps(is[l], w[j], in); j += 2; } - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm512_store_ps(p0 + p, rn); + _mm512_store_ps(p0 + p + 16, in); } }; - fp_type* rstate = state.get(); + __m512i idx[1 << L]; + __m512 w[1 << (1 + 2 * H + L)]; - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; + if (CH) { + auto m = GetMasks5(state.num_qubits(), qs, cqs, cvals); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); - for_.Run(size, f, w, idx, rstate); - } + unsigned r = 4 + H + cqs.size(); + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; + uint64_t size = uint64_t{1} << n; - void ApplyGate3HHH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; + for_.Run(size, f, w, m.imaskh, m.qmaskh, m.cvalsh, idx, state.get()); + } else { + auto m = GetMasks6(state.num_qubits(), qs, cqs, cvals); + FillPermutationIndices(m.qmaskl, idx); + FillControlledMatrixL( + m.cvalsl, m.cmaskl, m.qmaskl, matrix, (fp_type*) w); - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; + unsigned r = 4 + H + cqs.size() - m.cl; + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; + uint64_t size = uint64_t{1} << n; + + for_.Run(size, f, w, m.imaskh, m.qmaskh, m.cvalsh, idx, state.get()); } + } + template + std::complex ExpectationValueH(const std::vector& qs, + const fp_type* matrix, + const State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { + uint64_t imaskh, uint64_t qmaskh, const fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m512 ru, iu, rn, in; - __m512 rs[8], is[8]; + __m512 rs[hsize], is[hsize]; - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]); + auto p0 = rstate + _pdep_u64(i, imaskh); - auto p0 = rstate + 2 * k; + for (unsigned k = 0; k < hsize; ++k) { + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); + rs[k] = _mm512_load_ps(p0 + p); + is[k] = _mm512_load_ps(p0 + p + 16); } + double re = 0; + double im = 0; uint64_t j = 0; - for (unsigned l = 0; l < 8; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm512_set1_ps(v[j]); iu = _mm512_set1_ps(v[j + 1]); rn = _mm512_mul_ps(rs[0], ru); @@ -827,116 +701,72 @@ class SimulatorAVX512 final { j += 2; - for (unsigned n = 1; n < 8; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm512_set1_ps(v[j]); iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); + rn = _mm512_fmadd_ps(rs[l], ru, rn); + in = _mm512_fmadd_ps(rs[l], iu, in); + rn = _mm512_fnmadd_ps(is[l], iu, rn); + in = _mm512_fmadd_ps(is[l], ru, in); j += 2; } - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); + __m512 v_re = _mm512_fmadd_ps(is[k], in, _mm512_mul_ps(rs[k], rn)); + __m512 v_im = _mm512_fnmadd_ps(is[k], rn, _mm512_mul_ps(rs[k], in)); + + re += detail::HorizontalSumAVX512(v_re); + im += detail::HorizontalSumAVX512(v_im); } + + return std::complex{re, im}; }; - fp_type* rstate = state.get(); + auto m = GetMasks1(qs); - unsigned k = 7; + unsigned k = 4 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, matrix, ms, xss, rstate); + using Op = std::plus>; + return + for_.RunReduce(size, f, Op(), matrix, m.imaskh, m.qmaskh, state.get()); } - void ApplyGate3HHL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(9); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - + template + std::complex ExpectationValueL(const std::vector& qs, + const fp_type* matrix, + const State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, fp_type* rstate) { + uint64_t imaskh, uint64_t qmaskh, const __m512i* idx, + const fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; + __m512 rn, in; - __m512 rs[8], is[8]; + __m512 rs[gsize], is[gsize]; - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]); + auto p0 = rstate + _pdep_u64(i, imaskh); - auto p0 = rstate + 2 * k; + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); + rs[k2] = _mm512_load_ps(p0 + p); + is[k2] = _mm512_load_ps(p0 + p + 16); - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm512_permutexvar_ps(idx[l - 1], rs[k2]); + is[k2 + l] = _mm512_permutexvar_ps(idx[l - 1], is[k2]); } } + double re = 0; + double im = 0; uint64_t j = 0; - for (unsigned l = 0; l < 4; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm512_mul_ps(rs[0], w[j]); in = _mm512_mul_ps(rs[0], w[j + 1]); rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); @@ -944,7930 +774,58 @@ class SimulatorAVX512 final { j += 2; - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm512_fmadd_ps(rs[l], w[j], rn); + in = _mm512_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm512_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm512_fmadd_ps(is[l], w[j], in); j += 2; } - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate3HLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(8); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]); - - auto p0 = rstate + 2 * k; + unsigned m = lsize * k; - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); + __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); + __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } + re += detail::HorizontalSumAVX512(v_re); + im += detail::HorizontalSumAVX512(v_im); } - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } + return std::complex{re, im}; }; - fp_type* rstate = state.get(); + __m512i idx[1 << L]; + __m512 w[1 << (1 + 2 * H + L)]; - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + auto m = GetMasks2(qs); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); + + unsigned r = 4 + H; + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, w, ms, xss, idx, rstate); + using Op = std::plus>; + return + for_.RunReduce(size, f, Op(), w, m.imaskh, m.qmaskh, idx, state.get()); } - void ApplyGate3LLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - unsigned p[16]; - __m512i idx[7]; - - auto s = StateSpace::Create(7); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; + template + static void FillPermutationIndices(unsigned qmaskl, __m512i* idx) { + constexpr unsigned lsize = 1 << L; - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); + for (unsigned i = 0; i < lsize; ++i) { + unsigned p[16]; - for (unsigned i = 0; i < 7; ++i) { for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); + p[j] = MaskedAdd<4>(j, i + 1, qmaskl, lsize) | (j & (-1 ^ qmaskl)); } idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], p[9], p[8], p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 8 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - auto p0 = rstate + 32 * i; - - for (unsigned l = 0; l < 1; ++l) { - rs[8 * l] = _mm512_load_ps(p0); - is[8 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, idx, rstate); - } - - void ApplyGate4HHHH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[16], is[16]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]) | (256 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, rstate); - } - - void ApplyGate4HHHL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(11); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate4HHLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(10); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate4HLLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[7]; - - auto s = StateSpace::Create(9); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (128 * i + 16 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[8 * l] = _mm512_load_ps(p0 + xss[l]); - is[8 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate4LLLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - unsigned p[16]; - __m512i idx[15]; - - auto s = StateSpace::Create(8); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]) | (1 << qs[3]); - - for (unsigned i = 0; i < 15; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 16) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (256 * i + 16 * k + 16 * (m / 16) + (k + m) % 16); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - auto p0 = rstate + 32 * i; - - for (unsigned l = 0; l < 1; ++l) { - rs[16 * l] = _mm512_load_ps(p0); - is[16 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 16; ++j) { - rs[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[16 * l]); - is[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[16 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, idx, rstate); - } - - void ApplyGate5HHHHH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[32], is[32]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]) | (256 * i & ms[4]) | (512 * i & ms[5]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 32; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 9; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, rstate); - } - - void ApplyGate5HHHHL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(13); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 32 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[32], is[32]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]) | (256 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate5HHHLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(12); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (128 * i + 32 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[32], is[32]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate5HHLLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 3] + 1); - ms[i] = ((uint64_t{1} << qs[i + 3]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[7]; - - auto s = StateSpace::Create(11); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (256 * i + 32 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[32], is[32]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[8 * l] = _mm512_load_ps(p0 + xss[l]); - is[8 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate5HLLLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[4] + 1); - ms[0] = (uint64_t{1} << qs[4]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[15]; - - auto s = StateSpace::Create(10); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]) | (1 << qs[3]); - - for (unsigned i = 0; i < 15; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 16) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (512 * i + 32 * k + 16 * (m / 16) + (k + m) % 16); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[32], is[32]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[16 * l] = _mm512_load_ps(p0 + xss[l]); - is[16 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 16; ++j) { - rs[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[16 * l]); - is[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[16 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate6HHHHHH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[6]; - uint64_t ms[7]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 6; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[6] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[5] - 1); - - uint64_t xss[64]; - for (unsigned i = 0; i < 64; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 6; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[64], is[64]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]) | (256 * i & ms[4]) | (512 * i & ms[5]) - | (1024 * i & ms[6]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 64; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 64; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 10; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, rstate); - } - - void ApplyGate6HHHHHL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(15); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 32; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (128 * i + 64 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[64], is[64]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]) | (256 * i & ms[4]) | (512 * i & ms[5]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 32; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 9; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate6HHHHLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(14); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (256 * i + 64 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[64], is[64]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]) | (256 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate6HHHLLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 3] + 1); - ms[i] = ((uint64_t{1} << qs[i + 3]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[7]; - - auto s = StateSpace::Create(13); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (512 * i + 64 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[64], is[64]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[8 * l] = _mm512_load_ps(p0 + xss[l]); - is[8 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyGate6HHLLLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[4] + 1); - ms[0] = (uint64_t{1} << qs[4]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 4] + 1); - ms[i] = ((uint64_t{1} << qs[i + 4]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[15]; - - auto s = StateSpace::Create(12); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]) | (1 << qs[3]); - - for (unsigned i = 0; i < 15; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 16) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (1024 * i + 64 * k + 16 * (m / 16) + (k + m) % 16); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[64], is[64]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[16 * l] = _mm512_load_ps(p0 + xss[l]); - is[16 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 16; ++j) { - rs[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[16 * l]); - is[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[16 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, idx, rstate); - } - - void ApplyControlledGate1H_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[2], is[2]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate1H_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - - auto s = StateSpace::Create(6); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (2 * i + 2 * k + m); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 2 == (p[j] / 2) % 2 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[2], is[2]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate1L_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[2], is[2]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm512_load_ps(p0); - is[2 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate1L_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 2 == (p[j] / 2) % 2 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[2], is[2]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm512_load_ps(p0); - is[2 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate2HH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate2HH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - - auto s = StateSpace::Create(8); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (4 * i + 4 * k + m); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate2HL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(7); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate2HL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(7); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate2LL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(6); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm512_load_ps(p0); - is[4 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate2LL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(6); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm512_load_ps(p0); - is[4 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate3HHH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate3HHH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - - auto s = StateSpace::Create(10); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (8 * i + 8 * k + m); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate3HHL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(9); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate3HHL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(9); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate3HLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(8); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate3HLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(8); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate3LLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[7]; - - auto s = StateSpace::Create(7); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 8 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[8 * l] = _mm512_load_ps(p0); - is[8 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate3LLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[7]; - - auto s = StateSpace::Create(7); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 8 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[8 * l] = _mm512_load_ps(p0); - is[8 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate4HHHH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate4HHHH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - - auto s = StateSpace::Create(12); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 16 * k + m); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate4HHHL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(11); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate4HHHL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(11); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate4HHLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(10); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate4HHLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(10); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate4HLLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[7]; - - auto s = StateSpace::Create(9); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (128 * i + 16 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[8 * l] = _mm512_load_ps(p0 + xss[l]); - is[8 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate4HLLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[7]; - - auto s = StateSpace::Create(9); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (128 * i + 16 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[8 * l] = _mm512_load_ps(p0 + xss[l]); - is[8 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate4LLLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[15]; - - auto s = StateSpace::Create(8); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]) | (1 << qs[3]); - - for (unsigned i = 0; i < 15; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 16) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (256 * i + 16 * k + 16 * (m / 16) + (k + m) % 16); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[16 * l] = _mm512_load_ps(p0); - is[16 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 16; ++j) { - rs[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[16 * l]); - is[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[16 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - void ApplyControlledGate4LLLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[15]; - - auto s = StateSpace::Create(8); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]) | (1 << qs[3]); - - for (unsigned i = 0; i < 15; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 16) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (256 * i + 16 * k + 16 * (m / 16) + (k + m) % 16); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[16 * l] = _mm512_load_ps(p0); - is[16 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 16; ++j) { - rs[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[16 * l]); - is[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[16 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, idx, rstate); - } - - std::complex ExpectationValue1H(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[2], is[2]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - __m512 v_re = _mm512_fmadd_ps(is[l], in, _mm512_mul_ps(rs[l], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[l], rn, _mm512_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue1L(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[2], is[2]; - - auto p0 = rstate + 32 * i; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm512_load_ps(p0); - is[2 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - __m512 v_re = _mm512_fmadd_ps(is[l], in, _mm512_mul_ps(rs[l], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[l], rn, _mm512_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, idx, rstate); - } - - std::complex ExpectationValue2HH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[4], is[4]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - __m512 v_re = _mm512_fmadd_ps(is[l], in, _mm512_mul_ps(rs[l], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[l], rn, _mm512_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue2HL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(7); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[4], is[4]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 2 * l; - - __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue2LL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(6); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[4], is[4]; - - auto p0 = rstate + 32 * i; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm512_load_ps(p0); - is[4 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - __m512 v_re = _mm512_fmadd_ps(is[l], in, _mm512_mul_ps(rs[l], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[l], rn, _mm512_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, idx, rstate); - } - - std::complex ExpectationValue3HHH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[8], is[8]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - __m512 v_re = _mm512_fmadd_ps(is[l], in, _mm512_mul_ps(rs[l], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[l], rn, _mm512_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue3HHL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(9); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 2 * l; - - __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue3HLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(8); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 4 * l; - - __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue3LLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - unsigned p[16]; - __m512i idx[7]; - - auto s = StateSpace::Create(7); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 8 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - auto p0 = rstate + 32 * i; - - for (unsigned l = 0; l < 1; ++l) { - rs[8 * l] = _mm512_load_ps(p0); - is[8 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - __m512 v_re = _mm512_fmadd_ps(is[l], in, _mm512_mul_ps(rs[l], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[l], rn, _mm512_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, idx, rstate); - } - - std::complex ExpectationValue4HHHH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[16], is[16]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]) | (256 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - __m512 v_re = _mm512_fmadd_ps(is[l], in, _mm512_mul_ps(rs[l], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[l], rn, _mm512_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue4HHHL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(11); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 2 * l; - - __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue4HHLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(10); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 4 * l; - - __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue4HLLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[7]; - - auto s = StateSpace::Create(9); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (128 * i + 16 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[8 * l] = _mm512_load_ps(p0 + xss[l]); - is[8 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 8 * l; - - __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue4LLLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - unsigned p[16]; - __m512i idx[15]; - - auto s = StateSpace::Create(8); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]) | (1 << qs[3]); - - for (unsigned i = 0; i < 15; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 16) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (256 * i + 16 * k + 16 * (m / 16) + (k + m) % 16); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - auto p0 = rstate + 32 * i; - - for (unsigned l = 0; l < 1; ++l) { - rs[16 * l] = _mm512_load_ps(p0); - is[16 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 16; ++j) { - rs[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[16 * l]); - is[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[16 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - __m512 v_re = _mm512_fmadd_ps(is[l], in, _mm512_mul_ps(rs[l], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[l], rn, _mm512_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, idx, rstate); - } - - std::complex ExpectationValue5HHHHH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[32], is[32]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]) | (256 * i & ms[4]) | (512 * i & ms[5]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 32; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - __m512 v_re = _mm512_fmadd_ps(is[l], in, _mm512_mul_ps(rs[l], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[l], rn, _mm512_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 9; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue5HHHHL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(13); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 32 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[32], is[32]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]) | (256 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 2 * l; - - __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue5HHHLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(12); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (128 * i + 32 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[32], is[32]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 4 * l; - - __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue5HHLLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 3] + 1); - ms[i] = ((uint64_t{1} << qs[i + 3]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[7]; - - auto s = StateSpace::Create(11); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (256 * i + 32 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[32], is[32]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[8 * l] = _mm512_load_ps(p0 + xss[l]); - is[8 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 8 * l; - - __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue5HLLLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[4] + 1); - ms[0] = (uint64_t{1} << qs[4]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[15]; - - auto s = StateSpace::Create(10); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]) | (1 << qs[3]); - - for (unsigned i = 0; i < 15; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 16) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (512 * i + 32 * k + 16 * (m / 16) + (k + m) % 16); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[32], is[32]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[16 * l] = _mm512_load_ps(p0 + xss[l]); - is[16 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 16; ++j) { - rs[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[16 * l]); - is[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[16 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 16 * l; - - __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue6HHHHHH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[6]; - uint64_t ms[7]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 6; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[6] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[5] - 1); - - uint64_t xss[64]; - for (unsigned i = 0; i < 64; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 6; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[64], is[64]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]) | (256 * i & ms[4]) | (512 * i & ms[5]) - | (1024 * i & ms[6]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 64; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 64; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - __m512 v_re = _mm512_fmadd_ps(is[l], in, _mm512_mul_ps(rs[l], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[l], rn, _mm512_mul_ps(rs[l], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 10; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue6HHHHHL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = StateSpace::Create(15); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 32; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (128 * i + 64 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[64], is[64]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]) | (256 * i & ms[4]) | (512 * i & ms[5]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 32; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 2 * l; - - __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 9; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue6HHHHLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[3]; - - auto s = StateSpace::Create(14); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (256 * i + 64 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[64], is[64]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]) | (256 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 4 * l; - - __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue6HHHLLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 3] + 1); - ms[i] = ((uint64_t{1} << qs[i + 3]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[7]; - - auto s = StateSpace::Create(13); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (512 * i + 64 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[64], is[64]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]) - | (128 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[8 * l] = _mm512_load_ps(p0 + xss[l]); - is[8 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 8 * l; - - __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - std::complex ExpectationValue6HHLLLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[4] + 1); - ms[0] = (uint64_t{1} << qs[4]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 4] + 1); - ms[i] = ((uint64_t{1} << qs[i + 4]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[15]; - - auto s = StateSpace::Create(12); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]) | (1 << qs[3]); - - for (unsigned i = 0; i < 15; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 16) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (1024 * i + 64 * k + 16 * (m / 16) + (k + m) % 16); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, const fp_type* rstate) { - __m512 rn, in; - __m512 rs[64], is[64]; - - uint64_t k = (16 * i & ms[0]) | (32 * i & ms[1]) | (64 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[16 * l] = _mm512_load_ps(p0 + xss[l]); - is[16 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 16; ++j) { - rs[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[16 * l]); - is[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[16 * l]); - } - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - unsigned m = 16 * l; - - __m512 v_re = _mm512_fmadd_ps(is[m], in, _mm512_mul_ps(rs[m], rn)); - __m512 v_im = _mm512_fnmadd_ps(is[m], rn, _mm512_mul_ps(rs[m], in)); - - re += detail::HorizontalSumAVX512(v_re); - im += detail::HorizontalSumAVX512(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, idx, rstate); - } - - static unsigned MaskedAdd( - unsigned a, unsigned b, unsigned mask, unsigned lsize) { - unsigned c = bits::CompressBits(a, 4, mask); - return bits::ExpandBits((c + b) % lsize, 4, mask); } For for_; diff --git a/lib/simulator_basic.h b/lib/simulator_basic.h index 707825e8..9e136c68 100644 --- a/lib/simulator_basic.h +++ b/lib/simulator_basic.h @@ -15,12 +15,12 @@ #ifndef SIMULATOR_BASIC_H_ #define SIMULATOR_BASIC_H_ - -#include #include #include +#include +#include -#include "bits.h" +#include "simulator.h" #include "statespace_basic.h" namespace qsim { @@ -29,7 +29,7 @@ namespace qsim { * Quantum circuit simulator without vectorization. */ template -class SimulatorBasic final { +class SimulatorBasic final : public SimulatorBase { public: using StateSpace = StateSpaceBasic; using State = typename StateSpace::State; @@ -50,22 +50,22 @@ class SimulatorBasic final { switch (qs.size()) { case 1: - ApplyGate1H(qs, matrix, state); + ApplyGateH<1>(qs, matrix, state); break; case 2: - ApplyGate2H(qs, matrix, state); + ApplyGateH<2>(qs, matrix, state); break; case 3: - ApplyGate3H(qs, matrix, state); + ApplyGateH<3>(qs, matrix, state); break; case 4: - ApplyGate4H(qs, matrix, state); + ApplyGateH<4>(qs, matrix, state); break; case 5: - ApplyGate5H(qs, matrix, state); + ApplyGateH<5>(qs, matrix, state); break; case 6: - ApplyGate6H(qs, matrix, state); + ApplyGateH<6>(qs, matrix, state); break; default: // Not implemented. @@ -77,13 +77,15 @@ class SimulatorBasic final { * Applies a controlled gate using non-vectorized instructions. * @param qs Indices of the qubits affected by this gate. * @param cqs Indices of control qubits. - * @param cmask Bit mask of control qubit values. + * @param cvals Bit mask of control qubit values. * @param matrix Matrix representation of the gate to be applied. * @param state The state of the system, to be updated by this method. */ void ApplyControlledGate(const std::vector& qs, - const std::vector& cqs, uint64_t cmask, + const std::vector& cqs, uint64_t cvals, const fp_type* matrix, State& state) const { + // Assume qs[0] < qs[1] < qs[2] < ... . + if (cqs.size() == 0) { ApplyGate(qs, matrix, state); return; @@ -91,16 +93,16 @@ class SimulatorBasic final { switch (qs.size()) { case 1: - ApplyControlledGate1H(qs, cqs, cmask, matrix, state); + ApplyControlledGateH<1>(qs, cqs, cvals, matrix, state); break; case 2: - ApplyControlledGate2H(qs, cqs, cmask, matrix, state); + ApplyControlledGateH<2>(qs, cqs, cvals, matrix, state); break; case 3: - ApplyControlledGate3H(qs, cqs, cmask, matrix, state); + ApplyControlledGateH<3>(qs, cqs, cvals, matrix, state); break; case 4: - ApplyControlledGate4H(qs, cqs, cmask, matrix, state); + ApplyControlledGateH<4>(qs, cqs, cvals, matrix, state); break; default: // Not implemented. @@ -123,22 +125,22 @@ class SimulatorBasic final { switch (qs.size()) { case 1: - return ExpectationValue1H(qs, matrix, state); + return ExpectationValueH<1>(qs, matrix, state); break; case 2: - return ExpectationValue2H(qs, matrix, state); + return ExpectationValueH<2>(qs, matrix, state); break; case 3: - return ExpectationValue3H(qs, matrix, state); + return ExpectationValueH<3>(qs, matrix, state); break; case 4: - return ExpectationValue4H(qs, matrix, state); + return ExpectationValueH<4>(qs, matrix, state); break; case 5: - return ExpectationValue5H(qs, matrix, state); + return ExpectationValueH<5>(qs, matrix, state); break; case 6: - return ExpectationValue6H(qs, matrix, state); + return ExpectationValueH<6>(qs, matrix, state); break; default: // Not implemented. @@ -156,1088 +158,144 @@ class SimulatorBasic final { } private: - void ApplyGate1H(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - + template + void ApplyGateH(const std::vector& qs, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - fp_type rn, in; - fp_type rs[2], is[2]; - - uint64_t k = (1 * i & ms[0]) | (2 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 1; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, rstate); - } - - void ApplyGate2H(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } + const uint64_t* ms, const uint64_t* xss, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { fp_type rn, in; - fp_type rs[4], is[4]; - - uint64_t k = (1 * i & ms[0]) | (2 * i & ms[1]) | (4 * i & ms[2]); + fp_type rs[hsize], is[hsize]; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; } - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; + auto p0 = rstate + 2 * ii; - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 2; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, rstate); - } - - void ApplyGate3H(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - fp_type rn, in; - fp_type rs[8], is[8]; - - uint64_t k = (1 * i & ms[0]) | (2 * i & ms[1]) | (4 * i & ms[2]) - | (8 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = *(p0 + xss[k]); + is[k] = *(p0 + xss[k] + 1); } uint64_t j = 0; - for (unsigned l = 0; l < 8; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = rs[0] * v[j] - is[0] * v[j + 1]; in = rs[0] * v[j + 1] + is[0] * v[j]; j += 2; - for (unsigned n = 1; n < 8; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; + for (unsigned l = 1; l < hsize; ++l) { + rn += rs[l] * v[j] - is[l] * v[j + 1]; + in += rs[l] * v[j + 1] + is[l] * v[j]; j += 2; } - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; + *(p0 + xss[k]) = rn; + *(p0 + xss[k] + 1) = in; } }; - fp_type* rstate = state.get(); - - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, rstate); - } + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; - void ApplyGate4H(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; + FillIndices(state.num_qubits(), qs, ms, xss); - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - fp_type rn, in; - fp_type rs[16], is[16]; - - uint64_t k = (1 * i & ms[0]) | (2 * i & ms[1]) | (4 * i & ms[2]) - | (8 * i & ms[3]) | (16 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + unsigned n = state.num_qubits() > H ? state.num_qubits() - H : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, matrix, ms, xss, rstate); + for_.Run(size, f, matrix, ms, xss, state.get()); } - void ApplyGate5H(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - + template + void ApplyControlledGateH(const std::vector& qs, + const std::vector& cqs, + uint64_t cvals, const fp_type* matrix, + State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - fp_type rn, in; - fp_type rs[32], is[32]; - - uint64_t k = (1 * i & ms[0]) | (2 * i & ms[1]) | (4 * i & ms[2]) - | (8 * i & ms[3]) | (16 * i & ms[4]) | (32 * i & ms[5]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 32; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, rstate); - } - - void ApplyGate6H(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[6]; - uint64_t ms[7]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 6; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[6] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[5] - 1); - - uint64_t xss[64]; - for (unsigned i = 0; i < 64; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 6; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - fp_type rn, in; - fp_type rs[64], is[64]; - - uint64_t k = (1 * i & ms[0]) | (2 * i & ms[1]) | (4 * i & ms[2]) - | (8 * i & ms[3]) | (16 * i & ms[4]) | (32 * i & ms[5]) - | (64 * i & ms[6]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 64; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 64; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, rstate); - } - - void ApplyControlledGate1H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - fp_type rn, in; - fp_type rs[2], is[2]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 1 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate2H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - fp_type rn, in; - fp_type rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 2 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate3H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } + uint64_t cvalsh, uint64_t cmaskh, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; - emaskh = ~emaskh; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { fp_type rn, in; - fp_type rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; + fp_type rs[hsize], is[hsize]; - for (unsigned l = 0; l < 8; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; } - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; + if ((ii & cmaskh) == cvalsh) { + auto p0 = rstate + 2 * ii; - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate4H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = *(p0 + xss[k]); + is[k] = *(p0 + xss[k] + 1); } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - emaskh = ~emaskh; + uint64_t j = 0; - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - fp_type rn, in; - fp_type rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; + for (unsigned k = 0; k < hsize; ++k) { + rn = rs[0] * v[j] - is[0] * v[j + 1]; + in = rs[0] * v[j + 1] + is[0] * v[j]; j += 2; - } - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); + for (unsigned l = 1; l < hsize; ++l) { + rn += rs[l] * v[j] - is[l] * v[j + 1]; + in += rs[l] * v[j + 1] + is[l] * v[j]; - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; + j += 2; + } - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - std::complex ExpectationValue1H(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; + *(p0 + xss[k]) = rn; + *(p0 + xss[k] + 1) = in; } } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - fp_type rn, in; - fp_type rs[2], is[2]; - - uint64_t k = (1 * i & ms[0]) | (2 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - re += rs[l] * rn + is[l] * in; - im += rs[l] * in - is[l] * rn; - } - - return std::complex{re, im}; }; - const fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; - unsigned k = 1; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; + FillIndices(state.num_qubits(), qs, ms, xss); - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } + auto m = GetMasks7(state.num_qubits(), qs, cqs, cvals); - std::complex ExpectationValue2H(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - fp_type rn, in; - fp_type rs[4], is[4]; - - uint64_t k = (1 * i & ms[0]) | (2 * i & ms[1]) | (4 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - re += rs[l] * rn + is[l] * in; - im += rs[l] * in - is[l] * rn; - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 2; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + unsigned n = state.num_qubits() > H ? state.num_qubits() - H : 0; uint64_t size = uint64_t{1} << n; - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); + for_.Run(size, f, matrix, ms, xss, m.cvalsh, m.cmaskh, state.get()); } - std::complex ExpectationValue3H(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - + template + std::complex ExpectationValueH(const std::vector& qs, + const fp_type* matrix, + const State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, const uint64_t* ms, const uint64_t* xss, const fp_type* rstate) { - fp_type rn, in; - fp_type rs[8], is[8]; - - uint64_t k = (1 * i & ms[0]) | (2 * i & ms[1]) | (4 * i & ms[2]) - | (8 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - double re = 0; - double im = 0; + constexpr unsigned hsize = 1 << H; - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - re += rs[l] * rn + is[l] * in; - im += rs[l] * in - is[l] * rn; - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue4H(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { fp_type rn, in; - fp_type rs[16], is[16]; - - uint64_t k = (1 * i & ms[0]) | (2 * i & ms[1]) | (4 * i & ms[2]) - | (8 * i & ms[3]) | (16 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; + fp_type rs[hsize], is[hsize]; - j += 2; - } - - re += rs[l] * rn + is[l] * in; - im += rs[l] * in - is[l] * rn; + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; } - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } + auto p0 = rstate + 2 * ii; - std::complex ExpectationValue5H(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - fp_type rn, in; - fp_type rs[32], is[32]; - - uint64_t k = (1 * i & ms[0]) | (2 * i & ms[1]) | (4 * i & ms[2]) - | (8 * i & ms[3]) | (16 * i & ms[4]) | (32 * i & ms[5]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 32; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = *(p0 + xss[k]); + is[k] = *(p0 + xss[k] + 1); } double re = 0; @@ -1245,111 +303,36 @@ class SimulatorBasic final { uint64_t j = 0; - for (unsigned l = 0; l < 32; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = rs[0] * v[j] - is[0] * v[j + 1]; in = rs[0] * v[j + 1] + is[0] * v[j]; j += 2; - for (unsigned n = 1; n < 32; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; + for (unsigned l = 1; l < hsize; ++l) { + rn += rs[l] * v[j] - is[l] * v[j + 1]; + in += rs[l] * v[j + 1] + is[l] * v[j]; j += 2; } - re += rs[l] * rn + is[l] * in; - im += rs[l] * in - is[l] * rn; + re += rs[k] * rn + is[k] * in; + im += rs[k] * in - is[k] * rn; } return std::complex{re, im}; }; - const fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue6H(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[6]; - uint64_t ms[7]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 6; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[6] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[5] - 1); - - uint64_t xss[64]; - for (unsigned i = 0; i < 64; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 6; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - fp_type rn, in; - fp_type rs[64], is[64]; - - uint64_t k = (1 * i & ms[0]) | (2 * i & ms[1]) | (4 * i & ms[2]) - | (8 * i & ms[3]) | (16 * i & ms[4]) | (32 * i & ms[5]) - | (64 * i & ms[6]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 64; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 64; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - re += rs[l] * rn + is[l] * in; - im += rs[l] * in - is[l] * rn; - } - - return std::complex{re, im}; - }; + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; - const fp_type* rstate = state.get(); + FillIndices(state.num_qubits(), qs, ms, xss); - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + unsigned n = state.num_qubits() > H ? state.num_qubits() - H : 0; uint64_t size = uint64_t{1} << n; using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); + return for_.RunReduce(size, f, Op(), matrix, ms, xss, state.get()); } For for_; diff --git a/lib/simulator_sse.h b/lib/simulator_sse.h index 760a707a..c0347eb6 100644 --- a/lib/simulator_sse.h +++ b/lib/simulator_sse.h @@ -17,11 +17,12 @@ #include -#include #include #include +#include +#include -#include "bits.h" +#include "simulator.h" #include "statespace_sse.h" namespace qsim { @@ -30,7 +31,7 @@ namespace qsim { * Quantum circuit simulator with SSE vectorization. */ template -class SimulatorSSE final { +class SimulatorSSE final : public SimulatorBase { public: using StateSpace = StateSpaceSSE; using State = typename StateSpace::State; @@ -52,54 +53,54 @@ class SimulatorSSE final { switch (qs.size()) { case 1: if (qs[0] > 1) { - ApplyGate1H(qs, matrix, state); + ApplyGateH<1>(qs, matrix, state); } else { - ApplyGate1L(qs, matrix, state); + ApplyGateL<0, 1>(qs, matrix, state); } break; case 2: if (qs[0] > 1) { - ApplyGate2HH(qs, matrix, state); + ApplyGateH<2>(qs, matrix, state); } else if (qs[1] > 1) { - ApplyGate2HL(qs, matrix, state); + ApplyGateL<1, 1>(qs, matrix, state); } else { - ApplyGate2LL(qs, matrix, state); + ApplyGateL<0, 2>(qs, matrix, state); } break; case 3: if (qs[0] > 1) { - ApplyGate3HHH(qs, matrix, state); + ApplyGateH<3>(qs, matrix, state); } else if (qs[1] > 1) { - ApplyGate3HHL(qs, matrix, state); + ApplyGateL<2, 1>(qs, matrix, state); } else { - ApplyGate3HLL(qs, matrix, state); + ApplyGateL<1, 2>(qs, matrix, state); } break; case 4: if (qs[0] > 1) { - ApplyGate4HHHH(qs, matrix, state); + ApplyGateH<4>(qs, matrix, state); } else if (qs[1] > 1) { - ApplyGate4HHHL(qs, matrix, state); + ApplyGateL<3, 1>(qs, matrix, state); } else { - ApplyGate4HHLL(qs, matrix, state); + ApplyGateL<2, 2>(qs, matrix, state); } break; case 5: if (qs[0] > 1) { - ApplyGate5HHHHH(qs, matrix, state); + ApplyGateH<5>(qs, matrix, state); } else if (qs[1] > 1) { - ApplyGate5HHHHL(qs, matrix, state); + ApplyGateL<4, 1>(qs, matrix, state); } else { - ApplyGate5HHHLL(qs, matrix, state); + ApplyGateL<3, 2>(qs, matrix, state); } break; case 6: if (qs[0] > 1) { - ApplyGate6HHHHHH(qs, matrix, state); + ApplyGateH<6>(qs, matrix, state); } else if (qs[1] > 1) { - ApplyGate6HHHHHL(qs, matrix, state); + ApplyGateL<5, 1>(qs, matrix, state); } else { - ApplyGate6HHHHLL(qs, matrix, state); + ApplyGateL<4, 2>(qs, matrix, state); } break; default: @@ -112,13 +113,16 @@ class SimulatorSSE final { * Applies a controlled gate using SSE instructions. * @param qs Indices of the qubits affected by this gate. * @param cqs Indices of control qubits. - * @param cmask Bit mask of control qubit values. + * @param cvals Bit mask of control qubit values. * @param matrix Matrix representation of the gate to be applied. * @param state The state of the system, to be updated by this method. */ void ApplyControlledGate(const std::vector& qs, - const std::vector& cqs, uint64_t cmask, + const std::vector& cqs, uint64_t cvals, const fp_type* matrix, State& state) const { + // Assume qs[0] < qs[1] < qs[2] < ... . + // Assume cqs[0] < cqs[1] < cqs[2] < ... . + if (cqs.size() == 0) { ApplyGate(qs, matrix, state); return; @@ -128,78 +132,78 @@ class SimulatorSSE final { case 1: if (qs[0] > 1) { if (cqs[0] > 1) { - ApplyControlledGate1H_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate1H_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<1>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 1) { - ApplyControlledGate1L_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate1L_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 1, 0>(qs, cqs, cvals, matrix, state); } } break; case 2: if (qs[0] > 1) { if (cqs[0] > 1) { - ApplyControlledGate2HH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<2>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2HH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<2>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 1) { if (cqs[0] > 1) { - ApplyControlledGate2HL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2HL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 1, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 1) { - ApplyControlledGate2LL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2LL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 2, 0>(qs, cqs, cvals, matrix, state); } } break; case 3: if (qs[0] > 1) { if (cqs[0] > 1) { - ApplyControlledGate3HHH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<3>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HHH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<3>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 1) { if (cqs[0] > 1) { - ApplyControlledGate3HHL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HHL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 1, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 1) { - ApplyControlledGate3HLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 2, 0>(qs, cqs, cvals, matrix, state); } } break; case 4: if (qs[0] > 1) { if (cqs[0] > 1) { - ApplyControlledGate4HHHH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<4>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHHH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<4>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 1) { if (cqs[0] > 1) { - ApplyControlledGate4HHHL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<3, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHHL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<3, 1, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 1) { - ApplyControlledGate4HHLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 2, 0>(qs, cqs, cvals, matrix, state); } } break; @@ -224,54 +228,54 @@ class SimulatorSSE final { switch (qs.size()) { case 1: if (qs[0] > 1) { - return ExpectationValue1H(qs, matrix, state); + return ExpectationValueH<1>(qs, matrix, state); } else { - return ExpectationValue1L(qs, matrix, state); + return ExpectationValueL<0, 1>(qs, matrix, state); } break; case 2: if (qs[0] > 1) { - return ExpectationValue2HH(qs, matrix, state); + return ExpectationValueH<2>(qs, matrix, state); } else if (qs[1] > 1) { - return ExpectationValue2HL(qs, matrix, state); + return ExpectationValueL<1, 1>(qs, matrix, state); } else { - return ExpectationValue2LL(qs, matrix, state); + return ExpectationValueL<0, 2>(qs, matrix, state); } break; case 3: if (qs[0] > 1) { - return ExpectationValue3HHH(qs, matrix, state); + return ExpectationValueH<3>(qs, matrix, state); } else if (qs[1] > 1) { - return ExpectationValue3HHL(qs, matrix, state); + return ExpectationValueL<2, 1>(qs, matrix, state); } else { - return ExpectationValue3HLL(qs, matrix, state); + return ExpectationValueL<1, 2>(qs, matrix, state); } break; case 4: if (qs[0] > 1) { - return ExpectationValue4HHHH(qs, matrix, state); + return ExpectationValueH<4>(qs, matrix, state); } else if (qs[1] > 1) { - return ExpectationValue4HHHL(qs, matrix, state); + return ExpectationValueL<3, 1>(qs, matrix, state); } else { - return ExpectationValue4HHLL(qs, matrix, state); + return ExpectationValueL<2, 2>(qs, matrix, state); } break; case 5: if (qs[0] > 1) { - return ExpectationValue5HHHHH(qs, matrix, state); + return ExpectationValueH<5>(qs, matrix, state); } else if (qs[1] > 1) { - return ExpectationValue5HHHHL(qs, matrix, state); + return ExpectationValueL<4, 1>(qs, matrix, state); } else { - return ExpectationValue5HHHLL(qs, matrix, state); + return ExpectationValueL<3, 2>(qs, matrix, state); } break; case 6: if (qs[0] > 1) { - return ExpectationValue6HHHHHH(qs, matrix, state); + return ExpectationValueH<6>(qs, matrix, state); } else if (qs[1] > 1) { - return ExpectationValue6HHHHHL(qs, matrix, state); + return ExpectationValueL<5, 1>(qs, matrix, state); } else { - return ExpectationValue6HHHHLL(qs, matrix, state); + return ExpectationValueL<4, 2>(qs, matrix, state); } break; default: @@ -290,199 +294,34 @@ class SimulatorSSE final { } private: - void ApplyGate1H(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - + template + void ApplyGateH(const std::vector& qs, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[2], is[2]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, rstate); - } - - void ApplyGate1L(const std::vector& qs, - const fp_type* matrix, State& state) const { - unsigned p[4]; - - auto s = StateSpace::Create(3); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - unsigned q0, fp_type* rstate) { - __m128 rn, in; - __m128 rs[2], is[2]; - - auto p0 = rstate + 8 * i; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm_load_ps(p0); - is[2 * l] = _mm_load_ps(p0 + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0, rn); - _mm_store_ps(p0 + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 2; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; + const uint64_t* ms, const uint64_t* xss, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; - for_.Run(size, f, w, qs[0], rstate); - } + __m128 ru, iu, rn, in; + __m128 rs[hsize], is[hsize]; - void ApplyGate2HH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; + i *= 4; - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[4], is[4]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]); - auto p0 = rstate + 2 * k; + auto p0 = rstate + 2 * ii; - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = _mm_load_ps(p0 + xss[k]); + is[k] = _mm_load_ps(p0 + xss[k] + 4); } uint64_t j = 0; - for (unsigned l = 0; l < 4; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm_set1_ps(v[j]); iu = _mm_set1_ps(v[j + 1]); rn = _mm_mul_ps(rs[0], ru); @@ -492,182 +331,81 @@ class SimulatorSSE final { j += 2; - for (unsigned n = 1; n < 4; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm_set1_ps(v[j]); iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); + rn = _mm_add_ps(rn, _mm_mul_ps(rs[l], ru)); + in = _mm_add_ps(in, _mm_mul_ps(rs[l], iu)); + rn = _mm_sub_ps(rn, _mm_mul_ps(is[l], iu)); + in = _mm_add_ps(in, _mm_mul_ps(is[l], ru)); j += 2; } - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); + _mm_store_ps(p0 + xss[k], rn); + _mm_store_ps(p0 + xss[k] + 4, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; - unsigned k = 4; + FillIndices(state.num_qubits(), qs, ms, xss); + + unsigned k = 2 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, matrix, ms, xss, rstate); + for_.Run(size, f, matrix, ms, xss, state.get()); } - void ApplyGate2HL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(5); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - + template + void ApplyGateL(const std::vector& qs, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, const uint64_t* ms, const uint64_t* xss, unsigned q0, fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); + __m128 rn, in; + __m128 rs[gsize], is[gsize]; - j += 2; - } + i *= 4; - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - for_.Run(size, f, w, ms, xss, qs[0], rstate); - } - - void ApplyGate2LL(const std::vector& qs, - const fp_type* matrix, State& state) const { - unsigned p[4]; - - auto s = StateSpace::Create(4); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); + auto p0 = rstate + 2 * ii; - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } + rs[k2] = _mm_load_ps(p0 + xss[k]); + is[k2] = _mm_load_ps(p0 + xss[k] + 4); - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; + if (L == 1) { + rs[k2 + 1] = q0 == 0 ? _mm_shuffle_ps(rs[k2], rs[k2], 177) + : _mm_shuffle_ps(rs[k2], rs[k2], 78); + is[k2 + 1] = q0 == 0 ? _mm_shuffle_ps(is[k2], is[k2], 177) + : _mm_shuffle_ps(is[k2], is[k2], 78); + } else if (L == 2) { + rs[k2 + 1] = _mm_shuffle_ps(rs[k2], rs[k2], 57); + is[k2 + 1] = _mm_shuffle_ps(is[k2], is[k2], 57); + rs[k2 + 2] = _mm_shuffle_ps(rs[k2], rs[k2], 78); + is[k2 + 2] = _mm_shuffle_ps(is[k2], is[k2], 78); + rs[k2 + 3] = _mm_shuffle_ps(rs[k2], rs[k2], 147); + is[k2 + 3] = _mm_shuffle_ps(is[k2], is[k2], 147); } } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - auto p0 = rstate + 8 * i; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm_load_ps(p0); - is[4 * l] = _mm_load_ps(p0 + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } uint64_t j = 0; - for (unsigned l = 0; l < 1; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm_mul_ps(rs[0], w[j]); in = _mm_mul_ps(rs[0], w[j + 1]); rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); @@ -675,72 +413,68 @@ class SimulatorSSE final { j += 2; - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm_add_ps(rn, _mm_mul_ps(rs[l], w[j])); + in = _mm_add_ps(in, _mm_mul_ps(rs[l], w[j + 1])); + rn = _mm_sub_ps(rn, _mm_mul_ps(is[l], w[j + 1])); + in = _mm_add_ps(in, _mm_mul_ps(is[l], w[j])); j += 2; } - _mm_store_ps(p0, rn); - _mm_store_ps(p0 + 4, in); + _mm_store_ps(p0 + xss[k], rn); + _mm_store_ps(p0 + xss[k] + 4, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + __m128 w[1 << (1 + 2 * H + L)]; + + unsigned qmaskl = GetQMask(qs); - unsigned k = 2; + FillIndices(state.num_qubits(), qs, ms, xss); + FillMatrix(qmaskl, matrix, (fp_type*) w); + + unsigned k = 2 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, w, rstate); + for_.Run(size, f, w, ms, xss, qs[0], state.get()); } - void ApplyGate3HHH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - + template + void ApplyControlledGateHH(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { + const uint64_t* ms, const uint64_t* xss, uint64_t cvalsh, + uint64_t cmaskh, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m128 ru, iu, rn, in; - __m128 rs[8], is[8]; + __m128 rs[hsize], is[hsize]; + + i *= 4; + + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; + } - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]); + if ((ii & cmaskh) != cvalsh) return; - auto p0 = rstate + 2 * k; + auto p0 = rstate + 2 * ii; - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = _mm_load_ps(p0 + xss[k]); + is[k] = _mm_load_ps(p0 + xss[k] + 4); } uint64_t j = 0; - for (unsigned l = 0; l < 8; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm_set1_ps(v[j]); iu = _mm_set1_ps(v[j + 1]); rn = _mm_mul_ps(rs[0], ru); @@ -750,105 +484,67 @@ class SimulatorSSE final { j += 2; - for (unsigned n = 1; n < 8; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm_set1_ps(v[j]); iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); + rn = _mm_add_ps(rn, _mm_mul_ps(rs[l], ru)); + in = _mm_add_ps(in, _mm_mul_ps(rs[l], iu)); + rn = _mm_sub_ps(rn, _mm_mul_ps(is[l], iu)); + in = _mm_add_ps(in, _mm_mul_ps(is[l], ru)); j += 2; } - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); + _mm_store_ps(p0 + xss[k], rn); + _mm_store_ps(p0 + xss[k] + 4, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; - unsigned k = 5; + auto m = GetMasks7(state.num_qubits(), qs, cqs, cvals); + FillIndices(state.num_qubits(), qs, ms, xss); + + unsigned k = 2 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, matrix, ms, xss, rstate); + for_.Run(size, f, matrix, ms, xss, m.cvalsh, m.cmaskh, state.get()); } - void ApplyGate3HHL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(7); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } + template + void ApplyControlledGateHL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, + const uint64_t* ms, const uint64_t* xss, uint64_t cvalsh, + uint64_t cmaskh, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; - unsigned l = 2 * (8 * i + m); + __m128 rn, in; + __m128 rs[hsize], is[hsize]; - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } + i *= 4; - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned q0, fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]); - auto p0 = rstate + 2 * k; + if ((ii & cmaskh) != cvalsh) return; - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); + auto p0 = rstate + 2 * ii; - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = _mm_load_ps(p0 + xss[k]); + is[k] = _mm_load_ps(p0 + xss[k] + 4); } uint64_t j = 0; - for (unsigned l = 0; l < 4; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm_mul_ps(rs[0], w[j]); in = _mm_mul_ps(rs[0], w[j + 1]); rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); @@ -856,101 +552,85 @@ class SimulatorSSE final { j += 2; - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); + for (unsigned l = 1; l < hsize; ++l) { + rn = _mm_add_ps(rn, _mm_mul_ps(rs[l], w[j])); + in = _mm_add_ps(in, _mm_mul_ps(rs[l], w[j + 1])); + rn = _mm_sub_ps(rn, _mm_mul_ps(is[l], w[j + 1])); + in = _mm_add_ps(in, _mm_mul_ps(is[l], w[j])); j += 2; } - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); + _mm_store_ps(p0 + xss[k], rn); + _mm_store_ps(p0 + xss[k] + 4, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + __m128 w[1 << (1 + 2 * H)]; - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + auto m = GetMasks8<2>(state.num_qubits(), qs, cqs, cvals); + FillIndices(state.num_qubits(), qs, ms, xss); + FillControlledMatrixH(m.cvalsl, m.cmaskl, matrix, (fp_type*) w); + + unsigned r = 2 + H; + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, w, ms, xss, qs[0], rstate); + for_.Run(size, f, w, ms, xss, m.cvalsh, m.cmaskh, state.get()); } - void ApplyGate3HLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(6); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } + template + void ApplyControlledGateL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, + const uint64_t* ms, const uint64_t* xss, uint64_t cvalsh, + uint64_t cmaskh, unsigned q0, fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; - unsigned l = 2 * (8 * i + m); + __m128 rn, in; + __m128 rs[gsize], is[gsize]; - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } + i *= 4; - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; } - } - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; + if ((ii & cmaskh) != cvalsh) return; - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]); + auto p0 = rstate + 2 * ii; - auto p0 = rstate + 2 * k; + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); + rs[k2] = _mm_load_ps(p0 + xss[k]); + is[k2] = _mm_load_ps(p0 + xss[k] + 4); - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); + if (L == 1) { + rs[k2 + 1] = q0 == 0 ? _mm_shuffle_ps(rs[k2], rs[k2], 177) + : _mm_shuffle_ps(rs[k2], rs[k2], 78); + is[k2 + 1] = q0 == 0 ? _mm_shuffle_ps(is[k2], is[k2], 177) + : _mm_shuffle_ps(is[k2], is[k2], 78); + } else if (L == 2) { + rs[k2 + 1] = _mm_shuffle_ps(rs[k2], rs[k2], 57); + is[k2 + 1] = _mm_shuffle_ps(is[k2], is[k2], 57); + rs[k2 + 2] = _mm_shuffle_ps(rs[k2], rs[k2], 78); + is[k2 + 2] = _mm_shuffle_ps(is[k2], is[k2], 78); + rs[k2 + 3] = _mm_shuffle_ps(rs[k2], rs[k2], 147); + is[k2 + 3] = _mm_shuffle_ps(is[k2], is[k2], 147); + } } uint64_t j = 0; - for (unsigned l = 0; l < 2; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm_mul_ps(rs[0], w[j]); in = _mm_mul_ps(rs[0], w[j + 1]); rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); @@ -958,72 +638,76 @@ class SimulatorSSE final { j += 2; - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm_add_ps(rn, _mm_mul_ps(rs[l], w[j])); + in = _mm_add_ps(in, _mm_mul_ps(rs[l], w[j + 1])); + rn = _mm_sub_ps(rn, _mm_mul_ps(is[l], w[j + 1])); + in = _mm_add_ps(in, _mm_mul_ps(is[l], w[j])); j += 2; } - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); + _mm_store_ps(p0 + xss[k], rn); + _mm_store_ps(p0 + xss[k] + 4, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + __m128 w[1 << (1 + 2 * H + L)]; - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + FillIndices(state.num_qubits(), qs, ms, xss); + + unsigned r = 2 + H; + unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, w, ms, xss, rstate); - } + if (CH) { + auto m = GetMasks9(state.num_qubits(), qs, cqs, cvals); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); - void ApplyGate4HHHH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; + for_.Run(size, f, w, ms, xss, m.cvalsh, m.cmaskh, qs[0], state.get()); + } else { + auto m = GetMasks10(state.num_qubits(), qs, cqs, cvals); + FillControlledMatrixL( + m.cvalsl, m.cmaskl, m.qmaskl, matrix, (fp_type*) w); - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; + for_.Run(size, f, w, ms, xss, m.cvalsh, m.cmaskh, qs[0], state.get()); } + } + template + std::complex ExpectationValueH(const std::vector& qs, + const fp_type* matrix, + const State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { + const fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m128 ru, iu, rn, in; - __m128 rs[16], is[16]; + __m128 rs[hsize], is[hsize]; - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]) | (64 * i & ms[4]); + i *= 4; + + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; + } - auto p0 = rstate + 2 * k; + auto p0 = rstate + 2 * ii; - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = _mm_load_ps(p0 + xss[k]); + is[k] = _mm_load_ps(p0 + xss[k] + 4); } + double re = 0; + double im = 0; uint64_t j = 0; - for (unsigned l = 0; l < 16; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm_set1_ps(v[j]); iu = _mm_set1_ps(v[j + 1]); rn = _mm_mul_ps(rs[0], ru); @@ -1033,106 +717,90 @@ class SimulatorSSE final { j += 2; - for (unsigned n = 1; n < 16; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm_set1_ps(v[j]); iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); + rn = _mm_add_ps(rn, _mm_mul_ps(rs[l], ru)); + in = _mm_add_ps(in, _mm_mul_ps(rs[l], iu)); + rn = _mm_sub_ps(rn, _mm_mul_ps(is[l], iu)); + in = _mm_add_ps(in, _mm_mul_ps(is[l], ru)); j += 2; } - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); + __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[k], rn), _mm_mul_ps(is[k], in)); + __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[k], in), _mm_mul_ps(is[k], rn)); + + re += detail::HorizontalSumSSE(v_re); + im += detail::HorizontalSumSSE(v_im); } + + return std::complex{re, im}; }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + + FillIndices(state.num_qubits(), qs, ms, xss); - unsigned k = 6; + unsigned k = 2 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; - for_.Run(size, f, matrix, ms, xss, rstate); + using Op = std::plus>; + return for_.RunReduce(size, f, Op(), matrix, ms, xss, state.get()); } - void ApplyGate4HHHL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(9); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } + template + std::complex ExpectationValueL(const std::vector& qs, + const fp_type* matrix, + const State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, + const uint64_t* ms, const uint64_t* xss, unsigned q0, + const fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; - unsigned l = 2 * (16 * i + m); + __m128 rn, in; + __m128 rs[gsize], is[gsize]; - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } + i *= 4; - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } + uint64_t ii = i & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + i *= 2; + ii |= i & ms[j]; } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned q0, fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]); + auto p0 = rstate + 2 * ii; - auto p0 = rstate + 2 * k; + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); + rs[k2] = _mm_load_ps(p0 + xss[k]); + is[k2] = _mm_load_ps(p0 + xss[k] + 4); - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); + if (L == 1) { + rs[k2 + 1] = q0 == 0 ? _mm_shuffle_ps(rs[k2], rs[k2], 177) + : _mm_shuffle_ps(rs[k2], rs[k2], 78); + is[k2 + 1] = q0 == 0 ? _mm_shuffle_ps(is[k2], is[k2], 177) + : _mm_shuffle_ps(is[k2], is[k2], 78); + } else if (L == 2) { + rs[k2 + 1] = _mm_shuffle_ps(rs[k2], rs[k2], 57); + is[k2 + 1] = _mm_shuffle_ps(is[k2], is[k2], 57); + rs[k2 + 2] = _mm_shuffle_ps(rs[k2], rs[k2], 78); + is[k2 + 2] = _mm_shuffle_ps(is[k2], is[k2], 78); + rs[k2 + 3] = _mm_shuffle_ps(rs[k2], rs[k2], 147); + is[k2 + 3] = _mm_shuffle_ps(is[k2], is[k2], 147); + } } + double re = 0; + double im = 0; uint64_t j = 0; - for (unsigned l = 0; l < 8; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm_mul_ps(rs[0], w[j]); in = _mm_mul_ps(rs[0], w[j + 1]); rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); @@ -1140,5017 +808,42 @@ class SimulatorSSE final { j += 2; - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm_add_ps(rn, _mm_mul_ps(rs[l], w[j])); + in = _mm_add_ps(in, _mm_mul_ps(rs[l], w[j + 1])); + rn = _mm_sub_ps(rn, _mm_mul_ps(is[l], w[j + 1])); + in = _mm_add_ps(in, _mm_mul_ps(is[l], w[j])); j += 2; } - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); + unsigned m = lsize * k; + + __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[m], rn), _mm_mul_ps(is[m], in)); + __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[m], in), _mm_mul_ps(is[m], rn)); + + re += detail::HorizontalSumSSE(v_re); + im += detail::HorizontalSumSSE(v_im); } + + return std::complex{re, im}; }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + __m128 w[1 << (1 + 2 * H + L)]; - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; + unsigned qmaskl = GetQMask(qs); - for_.Run(size, f, w, ms, xss, qs[0], rstate); - } + FillIndices(state.num_qubits(), qs, ms, xss); + FillMatrix(qmaskl, matrix, (fp_type*) w); - void ApplyGate4HHLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(8); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, rstate); - } - - void ApplyGate5HHHHH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[32], is[32]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]) | (64 * i & ms[4]) | (128 * i & ms[5]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 32; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, rstate); - } - - void ApplyGate5HHHHL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(11); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (64 * i + 32 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned q0, fp_type* rstate) { - __m128 rn, in; - __m128 rs[32], is[32]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]) | (64 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, qs[0], rstate); - } - - void ApplyGate5HHHLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(10); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (128 * i + 32 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[32], is[32]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, rstate); - } - - void ApplyGate6HHHHHH(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[6]; - uint64_t ms[7]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 6; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[6] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[5] - 1); - - uint64_t xss[64]; - for (unsigned i = 0; i < 64; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 6; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[64], is[64]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]) | (64 * i & ms[4]) | (128 * i & ms[5]) - | (256 * i & ms[6]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 64; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 64; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, rstate); - } - - void ApplyGate6HHHHHL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(13); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 32; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (128 * i + 64 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned q0, fp_type* rstate) { - __m128 rn, in; - __m128 rs[64], is[64]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]) | (64 * i & ms[4]) | (128 * i & ms[5]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 32; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, qs[0], rstate); - } - - void ApplyGate6HHHHLL(const std::vector& qs, - const fp_type* matrix, State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(12); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (256 * i + 64 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[64], is[64]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]) | (64 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, rstate); - } - - void ApplyControlledGate1H_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[2], is[2]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate1H_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(4); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (2 * i + 2 * k + m); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 2 == (p[j] / 2) % 2 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[2], is[2]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate1L_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(3); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, fp_type* rstate) { - __m128 rn, in; - __m128 rs[2], is[2]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm_load_ps(p0); - is[2 * l] = _mm_load_ps(p0 + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0, rn); - _mm_store_ps(p0 + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 2 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, qs[0], rstate); - } - - void ApplyControlledGate1L_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(3); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 2 == (p[j] / 2) % 2 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, fp_type* rstate) { - __m128 rn, in; - __m128 rs[2], is[2]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm_load_ps(p0); - is[2 * l] = _mm_load_ps(p0 + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0, rn); - _mm_store_ps(p0 + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 2 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, qs[0], rstate); - } - - void ApplyControlledGate2HH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate2HH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(6); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (4 * i + 4 * k + m); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate2HL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(5); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, qs[0], rstate); - } - - void ApplyControlledGate2HL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(5); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, qs[0], rstate); - } - - void ApplyControlledGate2LL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(4); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm_load_ps(p0); - is[4 * l] = _mm_load_ps(p0 + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0, rn); - _mm_store_ps(p0 + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 2 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate2LL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(4); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm_load_ps(p0); - is[4 * l] = _mm_load_ps(p0 + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0, rn); - _mm_store_ps(p0 + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 2 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate3HHH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate3HHH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(8); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (8 * i + 8 * k + m); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate3HHL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(7); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, qs[0], rstate); - } - - void ApplyControlledGate3HHL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(7); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, qs[0], rstate); - } - - void ApplyControlledGate3HLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(6); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate3HLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(6); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate4HHHH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate4HHHH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(10); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 16 * k + m); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate4HHHL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(9); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, qs[0], rstate); - } - - void ApplyControlledGate4HHHL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(9); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, qs[0], rstate); - } - - void ApplyControlledGate4HHLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(8); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - void ApplyControlledGate4HHLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = StateSpace::Create(8); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t k = bits::ExpandBits(i, num_qubits, emaskh) | cmaskh; - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - for_.Run(size, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, rstate); - } - - std::complex ExpectationValue1H(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[2], is[2]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[l], rn), _mm_mul_ps(is[l], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[l], in), _mm_mul_ps(is[l], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue1L(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - unsigned p[4]; - - auto s = StateSpace::Create(3); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - unsigned q0, const fp_type* rstate) { - __m128 rn, in; - __m128 rs[2], is[2]; - - auto p0 = rstate + 8 * i; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm_load_ps(p0); - is[2 * l] = _mm_load_ps(p0 + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[l], rn), _mm_mul_ps(is[l], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[l], in), _mm_mul_ps(is[l], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 2; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, qs[0], rstate); - } - - std::complex ExpectationValue2HH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[4], is[4]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[l], rn), _mm_mul_ps(is[l], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[l], in), _mm_mul_ps(is[l], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue2HL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(5); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned q0, const fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - unsigned m = 2 * l; - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[m], rn), _mm_mul_ps(is[m], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[m], in), _mm_mul_ps(is[m], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, qs[0], rstate); - } - - std::complex ExpectationValue2LL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - unsigned p[4]; - - auto s = StateSpace::Create(4); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - auto p0 = rstate + 8 * i; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm_load_ps(p0); - is[4 * l] = _mm_load_ps(p0 + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[l], rn), _mm_mul_ps(is[l], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[l], in), _mm_mul_ps(is[l], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 2; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, rstate); - } - - std::complex ExpectationValue3HHH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[8], is[8]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[l], rn), _mm_mul_ps(is[l], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[l], in), _mm_mul_ps(is[l], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue3HHL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(7); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned q0, const fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - unsigned m = 2 * l; - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[m], rn), _mm_mul_ps(is[m], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[m], in), _mm_mul_ps(is[m], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, qs[0], rstate); - } - - std::complex ExpectationValue3HLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(6); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - unsigned m = 4 * l; - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[m], rn), _mm_mul_ps(is[m], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[m], in), _mm_mul_ps(is[m], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, rstate); - } - - std::complex ExpectationValue4HHHH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[16], is[16]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]) | (64 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[l], rn), _mm_mul_ps(is[l], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[l], in), _mm_mul_ps(is[l], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue4HHHL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(9); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned q0, const fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - unsigned m = 2 * l; - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[m], rn), _mm_mul_ps(is[m], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[m], in), _mm_mul_ps(is[m], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, qs[0], rstate); - } - - std::complex ExpectationValue4HHLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(8); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - unsigned m = 4 * l; - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[m], rn), _mm_mul_ps(is[m], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[m], in), _mm_mul_ps(is[m], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, rstate); - } - - std::complex ExpectationValue5HHHHH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[32], is[32]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]) | (64 * i & ms[4]) | (128 * i & ms[5]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 32; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[l], rn), _mm_mul_ps(is[l], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[l], in), _mm_mul_ps(is[l], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue5HHHHL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(11); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (64 * i + 32 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned q0, const fp_type* rstate) { - __m128 rn, in; - __m128 rs[32], is[32]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]) | (64 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - unsigned m = 2 * l; - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[m], rn), _mm_mul_ps(is[m], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[m], in), _mm_mul_ps(is[m], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, qs[0], rstate); - } - - std::complex ExpectationValue5HHHLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(10); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (128 * i + 32 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m128 rn, in; - __m128 rs[32], is[32]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 8; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - unsigned m = 4 * l; - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[m], rn), _mm_mul_ps(is[m], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[m], in), _mm_mul_ps(is[m], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, rstate); - } - - std::complex ExpectationValue6HHHHHH(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[6]; - uint64_t ms[7]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 6; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[6] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[5] - 1); - - uint64_t xss[64]; - for (unsigned i = 0; i < 64; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 6; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[64], is[64]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]) | (64 * i & ms[4]) | (128 * i & ms[5]) - | (256 * i & ms[6]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 64; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 64; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[l], rn), _mm_mul_ps(is[l], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[l], in), _mm_mul_ps(is[l], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), matrix, ms, xss, rstate); - } - - std::complex ExpectationValue6HHHHHL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(13); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 32; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (128 * i + 64 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned q0, const fp_type* rstate) { - __m128 rn, in; - __m128 rs[64], is[64]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]) | (64 * i & ms[4]) | (128 * i & ms[5]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 32; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - unsigned m = 2 * l; - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[m], rn), _mm_mul_ps(is[m], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[m], in), _mm_mul_ps(is[m], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - - using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, qs[0], rstate); - } - - std::complex ExpectationValue6HHHHLL(const std::vector& qs, - const fp_type* matrix, - const State& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = StateSpace::Create(12); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (256 * i + 64 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - const fp_type* rstate) { - __m128 rn, in; - __m128 rs[64], is[64]; - - uint64_t k = (4 * i & ms[0]) | (8 * i & ms[1]) | (16 * i & ms[2]) - | (32 * i & ms[3]) | (64 * i & ms[4]); - - auto p0 = rstate + 2 * k; - - for (unsigned l = 0; l < 16; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - double re = 0; - double im = 0; - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - unsigned m = 4 * l; - - __m128 v_re = _mm_add_ps(_mm_mul_ps(rs[m], rn), _mm_mul_ps(is[m], in)); - __m128 v_im = _mm_sub_ps(_mm_mul_ps(rs[m], in), _mm_mul_ps(is[m], rn)); - - re += detail::HorizontalSumSSE(v_re); - im += detail::HorizontalSumSSE(v_im); - } - - return std::complex{re, im}; - }; - - const fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; + unsigned k = 2 + H; + unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + uint64_t size = uint64_t{1} << n; using Op = std::plus>; - return for_.RunReduce(size, f, Op(), w, ms, xss, rstate); - } - - static unsigned MaskedAdd( - unsigned a, unsigned b, unsigned mask, unsigned lsize) { - unsigned c = bits::CompressBits(a, 2, mask); - return bits::ExpandBits((c + b) % lsize, 2, mask); + return for_.RunReduce(size, f, Op(), w, ms, xss, qs[0], state.get()); } For for_; diff --git a/pybind_interface/Makefile b/pybind_interface/Makefile index 6a7dc7eb..8450bd17 100644 --- a/pybind_interface/Makefile +++ b/pybind_interface/Makefile @@ -11,7 +11,7 @@ QSIMLIB_DECIDE = ../qsimcirq/qsim_decide`python3-config --extension-suffix` PYBINDFLAGS_BASIC = -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` PYBINDFLAGS_SSE = -msse4.1 -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` PYBINDFLAGS_AVX2 = -mavx2 -mfma -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` -PYBINDFLAGS_AVX512 = -mavx512f -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` +PYBINDFLAGS_AVX512 = -mavx512f -mbmi2 -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` # The flags for the compilation of GPU-specific Pybind11 interfaces PYBINDFLAGS_CUDA = -std=c++14 -x cu -Xcompiler "-Wall -shared -fPIC `python3 -m pybind11 --includes`" diff --git a/pybind_interface/avx512/CMakeLists.txt b/pybind_interface/avx512/CMakeLists.txt index c265fe13..705d39db 100644 --- a/pybind_interface/avx512/CMakeLists.txt +++ b/pybind_interface/avx512/CMakeLists.txt @@ -5,7 +5,7 @@ project(qsim) IF (WIN32) set(CMAKE_CXX_FLAGS "/arch:AVX512 /O2 /openmp") ELSE() - set(CMAKE_CXX_FLAGS "-mavx512f -O3 -fopenmp") + set(CMAKE_CXX_FLAGS "-mavx512f -mbmi2 -O3 -fopenmp") ENDIF() if(APPLE) diff --git a/tests/make.sh b/tests/make.sh index 5df5a37e..7fd828c8 100755 --- a/tests/make.sh +++ b/tests/make.sh @@ -24,21 +24,24 @@ path_to_lib=googletest/googletest/make/lib g++ -O3 -I$path_to_include -L$path_to_lib -o bitstring_test.x bitstring_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o channels_cirq_test.x channels_cirq_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -o circuit_qsim_parser_test.x circuit_qsim_parser_test.cc -lgtest -lpthread -g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o expect_test.x expect_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o expect_nobmi2_test.x expect_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -mbmi2 -fopenmp -o expect_test.x expect_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -o fuser_basic_test.x fuser_basic_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -o fuser_mqubit_test.x fuser_mqubit_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -o gates_qsim_test.x gates_qsim_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o hybrid_avx_test.x hybrid_avx_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -o matrix_test.x matrix_test.cc -lgtest -lpthread -g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o qtrajectory_avx_test.x qtrajectory_avx_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o qtrajectory_avx_nobmi2_test.x qtrajectory_avx_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -mbmi2 -o qtrajectory_avx_test.x qtrajectory_avx_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o run_qsim_test.x run_qsim_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o run_qsimh_test.x run_qsimh_test.cc -lgtest -lpthread -g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o simulator_avx_test.x simulator_avx_test.cc -lgtest -lpthread -g++ -O3 -I$path_to_include -L$path_to_lib -mavx512f -fopenmp -o simulator_avx512_test.x simulator_avx512_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o simulator_avx_nobmi2_test.x simulator_avx_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -mbmi2 -fopenmp -o simulator_avx_test.x simulator_avx_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -mavx512f -mbmi2 -fopenmp -o simulator_avx512_test.x simulator_avx512_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -fopenmp -o simulator_basic_test.x simulator_basic_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -msse4 -fopenmp -o simulator_sse_test.x simulator_sse_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o statespace_avx_test.x statespace_avx_test.cc -lgtest -lpthread -g++ -O3 -I$path_to_include -L$path_to_lib -mavx512f -fopenmp -o statespace_avx512_test.x statespace_avx512_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -mavx512f -mbmi2 -fopenmp -o statespace_avx512_test.x statespace_avx512_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -fopenmp -o statespace_basic_test.x statespace_basic_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -msse4 -fopenmp -o statespace_sse_test.x statespace_sse_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o unitary_calculator_avx_test.x unitary_calculator_avx_test.cc -lgtest -lpthread @@ -46,7 +49,7 @@ g++ -O3 -I$path_to_include -L$path_to_lib -mavx512f -mfma -fopenmp -o unitary_ca g++ -O3 -I$path_to_include -L$path_to_lib -fopenmp -o unitary_calculator_basic_test.x unitary_calculator_basic_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -msse4 -fopenmp -o unitary_calculator_sse_test.x unitary_calculator_sse_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o unitaryspace_avx_test.x unitaryspace_avx_test.cc -lgtest -lpthread -g++ -O3 -I$path_to_include -L$path_to_lib -mavx512f -mfma -fopenmp -o unitaryspace_avx512_test.x unitaryspace_avx512_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -mavx512f -mbmi2 -fopenmp -o unitaryspace_avx512_test.x unitaryspace_avx512_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -fopenmp -o unitaryspace_basic_test.x unitaryspace_basic_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -msse4 -fopenmp -o unitaryspace_sse_test.x unitaryspace_sse_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -o vectorspace_test.x vectorspace_test.cc -lgtest -lpthread From 4005bec50f13697ce59ea70a6dea28a9c010f828 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Wed, 1 Dec 2021 16:01:35 +0100 Subject: [PATCH 172/246] Add comments. --- lib/simulator.h | 44 ++++++++++++++++++++++++++++++++++++++++++-- lib/simulator_avx.h | 12 ++++++------ lib/simulator_sse.h | 8 ++++---- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/lib/simulator.h b/lib/simulator.h index a5b799f5..d5af3c2a 100644 --- a/lib/simulator.h +++ b/lib/simulator.h @@ -26,6 +26,16 @@ namespace qsim { */ class SimulatorBase { protected: + // The follwoing template parameters are used for functions below. + // H - the number of high (target) qubits. + // L - the number of low (target) qubits. + // R - SIMD register width in floats. + + // Fills the table of masks (ms) that is used to calculate base state indices + // and the table of offset indices (xss) that is used to access the state + // vector entries in matrix-vector multiplication functions. This function is + // used in simulator_basic.h, simulator_sse.h and simulator_avx.h (no bmi2 + // version). template static void FillIndices(unsigned num_qubits, const std::vector& qs, uint64_t* ms, uint64_t* xss) { @@ -50,6 +60,7 @@ class SimulatorBase { } } + // Fills gate matrix entries for gates with low qubits. template static void FillMatrix(unsigned qmaskl, const fp_type* matrix, fp_type* w) { constexpr unsigned gsize = 1 << (H + L); @@ -78,6 +89,8 @@ class SimulatorBase { } } + // Fills gate matrix entries for controlled gates with high target qubits + // and low control qubits. template static void FillControlledMatrixH(uint64_t cvalsl, uint64_t cmaskl, const fp_type* matrix, fp_type* w) { @@ -103,6 +116,8 @@ class SimulatorBase { } } + // Fills gate matrix entries for controlled gates with low target qubits + // and low control qubits. template static void FillControlledMatrixL(uint64_t cvalsl, uint64_t cmaskl, unsigned qmaskl, const fp_type* matrix, @@ -135,6 +150,27 @@ class SimulatorBase { } } +/* + The GetMasks* functions below provide various masks and related values. + GetMasks1, GetMasks2, GetMasks3, GetMasks4, GetMasks5 and GetMasks6 are + used in simulator_avx.h (BMI2 version) and in simulator_avx512.h. GetMasks7, + GetMasks8, GetMasks9, GetMasks10 and GetMasks11 are used in simulator_avx.h + (no BMI2 version) and in simulator_sse.h. + + imaskh - inverted mask of high qubits (high control and target qubits). + qmaskh - mask of high qubits (high target qubits). + cvalsh - control bit values of high control qubits placed in correct + positions. + cvalsl - control bit values of low control qubits placed in correct positions. + cmaskh - mask of high control qubits. + cmaskl - mask of low control qubits. + qmaskl - mask of low qubits (low target qubits). + cl - the number of low control qubits. + + Note that imaskh, qmaskh and cvalsh are multiplied by two in GetMasks1, + GetMasks2, GetMasks3, GetMasks4, GetMasks5 and GetMasks6. +*/ + struct Masks1 { uint64_t imaskh; uint64_t qmaskh; @@ -423,15 +459,19 @@ class SimulatorBase { return {cvalsh, cmaskh, cvalsl, cmaskl, qmaskl}; } + struct Masks11 { + unsigned qmaskl; + }; + template - static unsigned GetQMask(const std::vector& qs) { + static Masks11 GetMasks11(const std::vector& qs) { unsigned qmaskl = 0; for (unsigned i = 0; i < L; ++i) { qmaskl |= 1 << qs[i]; } - return qmaskl; + return {qmaskl}; } template diff --git a/lib/simulator_avx.h b/lib/simulator_avx.h index cb596808..351e0832 100644 --- a/lib/simulator_avx.h +++ b/lib/simulator_avx.h @@ -926,11 +926,11 @@ class SimulatorAVX final : public SimulatorBase { __m256i idx[1 << L]; __m256 w[1 << (1 + 2 * H + L)]; - unsigned qmaskl = GetQMask(qs); + auto m = GetMasks11(qs); FillIndices(state.num_qubits(), qs, ms, xss); - FillPermutationIndices(qmaskl, idx); - FillMatrix(qmaskl, matrix, (fp_type*) w); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); unsigned r = 3 + H; unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; @@ -1315,11 +1315,11 @@ class SimulatorAVX final : public SimulatorBase { __m256i idx[1 << L]; __m256 w[1 << (1 + 2 * H + L)]; - unsigned qmaskl = GetQMask(qs); + auto m = GetMasks11(qs); FillIndices(state.num_qubits(), qs, ms, xss); - FillPermutationIndices(qmaskl, idx); - FillMatrix(qmaskl, matrix, (fp_type*) w); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); unsigned r = 3 + H; unsigned n = state.num_qubits() > r ? state.num_qubits() - r : 0; diff --git a/lib/simulator_sse.h b/lib/simulator_sse.h index c0347eb6..50279e22 100644 --- a/lib/simulator_sse.h +++ b/lib/simulator_sse.h @@ -431,10 +431,10 @@ class SimulatorSSE final : public SimulatorBase { uint64_t xss[1 << H]; __m128 w[1 << (1 + 2 * H + L)]; - unsigned qmaskl = GetQMask(qs); + auto m = GetMasks11(qs); FillIndices(state.num_qubits(), qs, ms, xss); - FillMatrix(qmaskl, matrix, (fp_type*) w); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); unsigned k = 2 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; @@ -833,10 +833,10 @@ class SimulatorSSE final : public SimulatorBase { uint64_t xss[1 << H]; __m128 w[1 << (1 + 2 * H + L)]; - unsigned qmaskl = GetQMask(qs); + auto m = GetMasks11(qs); FillIndices(state.num_qubits(), qs, ms, xss); - FillMatrix(qmaskl, matrix, (fp_type*) w); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); unsigned k = 2 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; From 852792144e6bbee89e0d38adb141d153b579770e Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Thu, 2 Dec 2021 16:47:49 +0100 Subject: [PATCH 173/246] Update unitary calculators. --- lib/BUILD | 8 +- lib/unitary_calculator_avx.h | 5687 +++------------------------- lib/unitary_calculator_avx512.h | 6255 ++----------------------------- lib/unitary_calculator_basic.h | 833 +--- lib/unitary_calculator_sse.h | 4446 ++-------------------- tests/make.sh | 5 +- 6 files changed, 1115 insertions(+), 16119 deletions(-) diff --git a/lib/BUILD b/lib/BUILD index e40a58d3..d69ae1da 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -634,7 +634,7 @@ cc_library( name = "unitary_calculator_avx", hdrs = ["unitary_calculator_avx.h"], deps = [ - ":bits", + ":simulator_base", ":unitaryspace_avx", ], ) @@ -643,7 +643,7 @@ cc_library( name = "unitary_calculator_avx512", hdrs = ["unitary_calculator_avx512.h"], deps = [ - ":bits", + ":simulator_base", ":unitaryspace_avx512", ], ) @@ -652,7 +652,7 @@ cc_library( name = "unitary_calculator_basic", hdrs = ["unitary_calculator_basic.h"], deps = [ - ":bits", + ":simulator_base", ":unitaryspace_basic", ], ) @@ -661,7 +661,7 @@ cc_library( name = "unitary_calculator_sse", hdrs = ["unitary_calculator_sse.h"], deps = [ - ":bits", + ":simulator_base", ":unitaryspace_sse", ], ) diff --git a/lib/unitary_calculator_avx.h b/lib/unitary_calculator_avx.h index 519ff26c..2bcb6780 100644 --- a/lib/unitary_calculator_avx.h +++ b/lib/unitary_calculator_avx.h @@ -17,21 +17,22 @@ #include -#include #include #include +#include +#include -#include "bits.h" +#include "simulator.h" #include "unitaryspace_avx.h" namespace qsim { namespace unitary { /** - * Quantum circuit unitary calculator with AVX vectorization. + * Quantum circuit simulator with AVX vectorization. */ template -class UnitaryCalculatorAVX final { +class UnitaryCalculatorAVX final : public SimulatorBase { public: using UnitarySpace = UnitarySpaceAVX; using Unitary = typename UnitarySpace::Unitary; @@ -50,68 +51,68 @@ class UnitaryCalculatorAVX final { * @param state The state of the system, to be updated by this method. */ void ApplyGate(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { + const fp_type* matrix, State& state) const { // Assume qs[0] < qs[1] < qs[2] < ... . switch (qs.size()) { case 1: if (qs[0] > 2) { - ApplyGate1H(qs, matrix, state); + ApplyGateH<1>(qs, matrix, state); } else { - ApplyGate1L(qs, matrix, state); + ApplyGateL<0, 1>(qs, matrix, state); } break; case 2: if (qs[0] > 2) { - ApplyGate2HH(qs, matrix, state); + ApplyGateH<2>(qs, matrix, state); } else if (qs[1] > 2) { - ApplyGate2HL(qs, matrix, state); + ApplyGateL<1, 1>(qs, matrix, state); } else { - ApplyGate2LL(qs, matrix, state); + ApplyGateL<0, 2>(qs, matrix, state); } break; case 3: if (qs[0] > 2) { - ApplyGate3HHH(qs, matrix, state); + ApplyGateH<3>(qs, matrix, state); } else if (qs[1] > 2) { - ApplyGate3HHL(qs, matrix, state); + ApplyGateL<2, 1>(qs, matrix, state); } else if (qs[2] > 2) { - ApplyGate3HLL(qs, matrix, state); + ApplyGateL<1, 2>(qs, matrix, state); } else { - ApplyGate3LLL(qs, matrix, state); + ApplyGateL<0, 3>(qs, matrix, state); } break; case 4: if (qs[0] > 2) { - ApplyGate4HHHH(qs, matrix, state); + ApplyGateH<4>(qs, matrix, state); } else if (qs[1] > 2) { - ApplyGate4HHHL(qs, matrix, state); + ApplyGateL<3, 1>(qs, matrix, state); } else if (qs[2] > 2) { - ApplyGate4HHLL(qs, matrix, state); + ApplyGateL<2, 2>(qs, matrix, state); } else { - ApplyGate4HLLL(qs, matrix, state); + ApplyGateL<1, 3>(qs, matrix, state); } break; case 5: if (qs[0] > 2) { - ApplyGate5HHHHH(qs, matrix, state); + ApplyGateH<5>(qs, matrix, state); } else if (qs[1] > 2) { - ApplyGate5HHHHL(qs, matrix, state); + ApplyGateL<4, 1>(qs, matrix, state); } else if (qs[2] > 2) { - ApplyGate5HHHLL(qs, matrix, state); + ApplyGateL<3, 2>(qs, matrix, state); } else { - ApplyGate5HHLLL(qs, matrix, state); + ApplyGateL<2, 3>(qs, matrix, state); } break; case 6: if (qs[0] > 2) { - ApplyGate6HHHHHH(qs, matrix, state); + ApplyGateH<6>(qs, matrix, state); } else if (qs[1] > 2) { - ApplyGate6HHHHHL(qs, matrix, state); + ApplyGateL<5, 1>(qs, matrix, state); } else if (qs[2] > 2) { - ApplyGate6HHHHLL(qs, matrix, state); + ApplyGateL<4, 2>(qs, matrix, state); } else { - ApplyGate6HHHLLL(qs, matrix, state); + ApplyGateL<3, 3>(qs, matrix, state); } break; default: @@ -124,13 +125,16 @@ class UnitaryCalculatorAVX final { * Applies a controlled gate using AVX instructions. * @param qs Indices of the qubits affected by this gate. * @param cqs Indices of control qubits. - * @param cmask Bit mask of control qubit values. + * @param cvals Bit mask of control qubit values. * @param matrix Matrix representation of the gate to be applied. * @param state The state of the system, to be updated by this method. */ void ApplyControlledGate(const std::vector& qs, - const std::vector& cqs, uint64_t cmask, - const fp_type* matrix, Unitary& state) const { + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { + // Assume qs[0] < qs[1] < qs[2] < ... . + // Assume cqs[0] < cqs[1] < cqs[2] < ... . + if (cqs.size() == 0) { ApplyGate(qs, matrix, state); return; @@ -140,90 +144,90 @@ class UnitaryCalculatorAVX final { case 1: if (qs[0] > 2) { if (cqs[0] > 2) { - ApplyControlledGate1H_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate1H_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<1>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 2) { - ApplyControlledGate1L_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate1L_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 1, 0>(qs, cqs, cvals, matrix, state); } } break; case 2: if (qs[0] > 2) { if (cqs[0] > 2) { - ApplyControlledGate2HH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<2>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2HH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<2>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 2) { if (cqs[0] > 2) { - ApplyControlledGate2HL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2HL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 1, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 2) { - ApplyControlledGate2LL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2LL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 2, 0>(qs, cqs, cvals, matrix, state); } } break; case 3: if (qs[0] > 2) { if (cqs[0] > 2) { - ApplyControlledGate3HHH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<3>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HHH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<3>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 2) { if (cqs[0] > 2) { - ApplyControlledGate3HHL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HHL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 1, 0>(qs, cqs, cvals, matrix, state); } } else if (qs[2] > 2) { if (cqs[0] > 2) { - ApplyControlledGate3HLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 2, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 2) { - ApplyControlledGate3LLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 3, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3LLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 3, 0>(qs, cqs, cvals, matrix, state); } } break; case 4: if (qs[0] > 2) { if (cqs[0] > 2) { - ApplyControlledGate4HHHH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<4>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHHH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<4>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 2) { if (cqs[0] > 2) { - ApplyControlledGate4HHHL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<3, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHHL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<3, 1, 0>(qs, cqs, cvals, matrix, state); } } else if (qs[2] > 2) { if (cqs[0] > 2) { - ApplyControlledGate4HHLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 2, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 2) { - ApplyControlledGate4HLLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 3, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HLLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 3, 0>(qs, cqs, cvals, matrix, state); } } break; @@ -241,46 +245,35 @@ class UnitaryCalculatorAVX final { } private: - void ApplyGate1H(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } +#ifdef __BMI2__ + + template + void ApplyGateH(const std::vector& qs, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { + uint64_t imaskh, uint64_t qmaskh, uint64_t size, + uint64_t row_size, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m256 ru, iu, rn, in; - __m256 rs[2], is[2]; + __m256 rs[hsize], is[hsize]; - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]); + uint64_t r = i % size; + uint64_t s = i / size; - auto p0 = rstate + row_size * r + 2 * c; + auto p0 = rstate + row_size * s + _pdep_u64(r, imaskh); - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); + for (unsigned k = 0; k < hsize; ++k) { + uint64_t p = _pdep_u64(k, qmaskh); + + rs[k] = _mm256_load_ps(p0 + p); + is[k] = _mm256_load_ps(p0 + p + 8); } uint64_t j = 0; - for (unsigned l = 0; l < 2; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm256_set1_ps(v[j]); iu = _mm256_set1_ps(v[j + 1]); rn = _mm256_mul_ps(rs[0], ru); @@ -290,94 +283,70 @@ class UnitaryCalculatorAVX final { j += 2; - for (unsigned n = 1; n < 2; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm256_set1_ps(v[j]); iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); + rn = _mm256_fmadd_ps(rs[l], ru, rn); + in = _mm256_fmadd_ps(rs[l], iu, in); + rn = _mm256_fnmadd_ps(is[l], iu, rn); + in = _mm256_fmadd_ps(is[l], ru, in); j += 2; } - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm256_store_ps(p0 + p, rn); + _mm256_store_ps(p0 + p + 8, in); } }; - fp_type* rstate = state.get(); + auto m = GetMasks1(qs); - unsigned k = 4; + unsigned k = 3 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); + for_.Run(size * size2, f, + matrix, m.imaskh, m.qmaskh, size, raw_size, state.get()); } - void ApplyGate1L(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - unsigned p[8]; - __m256i idx[1]; - - auto s = UnitarySpace::Create(2); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); + template + void ApplyGateL(const std::vector& qs, + const fp_type* matrix, State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, + uint64_t imaskh, uint64_t qmaskh, const __m256i* idx, + uint64_t size, uint64_t row_size, fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } + __m256 rn, in; + __m256 rs[gsize], is[gsize]; - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } + uint64_t r = i % size; + uint64_t s = i / size; - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[2], is[2]; + auto p0 = rstate + row_size * s + _pdep_u64(r, imaskh); - uint64_t ii = i % size; - uint64_t r = i / size; - auto p0 = rstate + row_size * r + 16 * ii; + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm256_load_ps(p0); - is[2 * l] = _mm256_load_ps(p0 + 8); + rs[k2] = _mm256_load_ps(p0 + p); + is[k2] = _mm256_load_ps(p0 + p + 8); - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm256_permutevar8x32_ps(rs[k2], idx[l - 1]); + is[k2 + l] = _mm256_permutevar8x32_ps(is[k2], idx[l - 1]); } } uint64_t j = 0; - for (unsigned l = 0; l < 1; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm256_mul_ps(rs[0], w[j]); in = _mm256_mul_ps(rs[0], w[j + 1]); rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); @@ -385,75 +354,66 @@ class UnitaryCalculatorAVX final { j += 2; - for (unsigned n = 1; n < 2; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm256_fmadd_ps(rs[l], w[j], rn); + in = _mm256_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm256_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm256_fmadd_ps(is[l], w[j], in); j += 2; } - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm256_store_ps(p0 + p, rn); + _mm256_store_ps(p0 + p + 8, in); } }; - fp_type* rstate = state.get(); + __m256i idx[1 << L]; + __m256 w[1 << (1 + 2 * H + L)]; + + auto m = GetMasks2(qs); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); - unsigned k = 3; + unsigned k = 3 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, w, idx, size, raw_size, rstate); + for_.Run(size * size2, f, + w, m.imaskh, m.qmaskh, idx, size, raw_size, state.get()); } - void ApplyGate2HH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - + template + void ApplyControlledGateHH(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, + uint64_t imaskh, uint64_t qmaskh, uint64_t cvalsh, uint64_t size, uint64_t row_size, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m256 ru, iu, rn, in; - __m256 rs[4], is[4]; + __m256 rs[hsize], is[hsize]; + + uint64_t r = i % size; + uint64_t s = i / size; - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]) | (32 * ii & ms[2]); + auto p0 = rstate + row_size * s + (_pdep_u64(r, imaskh) | cvalsh); - auto p0 = rstate + row_size * r + 2 * c; + for (unsigned k = 0; k < hsize; ++k) { + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); + rs[k] = _mm256_load_ps(p0 + p); + is[k] = _mm256_load_ps(p0 + p + 8); } uint64_t j = 0; - for (unsigned l = 0; l < 4; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm256_set1_ps(v[j]); iu = _mm256_set1_ps(v[j + 1]); rn = _mm256_mul_ps(rs[0], ru); @@ -463,115 +423,63 @@ class UnitaryCalculatorAVX final { j += 2; - for (unsigned n = 1; n < 4; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm256_set1_ps(v[j]); iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); + rn = _mm256_fmadd_ps(rs[l], ru, rn); + in = _mm256_fmadd_ps(rs[l], iu, in); + rn = _mm256_fnmadd_ps(is[l], iu, rn); + in = _mm256_fmadd_ps(is[l], ru, in); j += 2; } - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm256_store_ps(p0 + p, rn); + _mm256_store_ps(p0 + p + 8, in); } }; - fp_type* rstate = state.get(); + auto m = GetMasks3(state.num_qubits(), qs, cqs, cvals); - unsigned k = 5; + unsigned k = 3 + H + cqs.size(); unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); + for_.Run(size * size2, f, + matrix, m.imaskh, m.qmaskh, m.cvalsh, size, raw_size, state.get()); } - void ApplyGate2HL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[1]; - - auto s = UnitarySpace::Create(3); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - + template + void ApplyControlledGateHL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { + uint64_t imaskh, uint64_t qmaskh, uint64_t cvalsh, + uint64_t size, uint64_t row_size, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m256 rn, in; - __m256 rs[4], is[4]; + __m256 rs[hsize], is[hsize]; - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]); + uint64_t r = i % size; + uint64_t s = i / size; - auto p0 = rstate + row_size * r + 2 * c; + auto p0 = rstate + row_size * s + (_pdep_u64(r, imaskh) | cvalsh); - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); + for (unsigned k = 0; k < hsize; ++k) { + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } + rs[k] = _mm256_load_ps(p0 + p); + is[k] = _mm256_load_ps(p0 + p + 8); } uint64_t j = 0; - for (unsigned l = 0; l < 2; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm256_mul_ps(rs[0], w[j]); in = _mm256_mul_ps(rs[0], w[j + 1]); rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); @@ -579,92 +487,73 @@ class UnitaryCalculatorAVX final { j += 2; - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < hsize; ++l) { + rn = _mm256_fmadd_ps(rs[l], w[j], rn); + in = _mm256_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm256_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm256_fmadd_ps(is[l], w[j], in); j += 2; } - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm256_store_ps(p0 + p, rn); + _mm256_store_ps(p0 + p + 8, in); } }; - fp_type* rstate = state.get(); + __m256 w[1 << (1 + 2 * H)]; + + auto m = GetMasks4(state.num_qubits(), qs, cqs, cvals); + FillControlledMatrixH(m.cvalsl, m.cmaskl, matrix, (fp_type*) w); - unsigned k = 4; + unsigned k = 3 + H + cqs.size() - m.cl; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); + for_.Run(size * size2, f, + w, m.imaskh, m.qmaskh, m.cvalsh, size, raw_size, state.get()); } - void ApplyGate2LL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - unsigned p[8]; - __m256i idx[3]; - - auto s = UnitarySpace::Create(3); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - + template + void ApplyControlledGateL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, + uint64_t imaskh, uint64_t qmaskh, uint64_t cvalsh, const __m256i* idx, uint64_t size, uint64_t row_size, fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; + __m256 rn, in; - __m256 rs[4], is[4]; + __m256 rs[gsize], is[gsize]; + + uint64_t r = i % size; + uint64_t s = i / size; - uint64_t ii = i % size; - uint64_t r = i / size; - auto p0 = rstate + row_size * r + 16 * ii; + auto p0 = rstate + row_size * s + (_pdep_u64(r, imaskh) | cvalsh); - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm256_load_ps(p0); - is[4 * l] = _mm256_load_ps(p0 + 8); + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); + rs[k2] = _mm256_load_ps(p0 + p); + is[k2] = _mm256_load_ps(p0 + p + 8); + + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm256_permutevar8x32_ps(rs[k2], idx[l - 1]); + is[k2 + l] = _mm256_permutevar8x32_ps(is[k2], idx[l - 1]); } } uint64_t j = 0; - for (unsigned l = 0; l < 1; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm256_mul_ps(rs[0], w[j]); in = _mm256_mul_ps(rs[0], w[j + 1]); rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); @@ -672,76 +561,86 @@ class UnitaryCalculatorAVX final { j += 2; - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm256_fmadd_ps(rs[l], w[j], rn); + in = _mm256_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm256_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm256_fmadd_ps(is[l], w[j], in); j += 2; } - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm256_store_ps(p0 + p, rn); + _mm256_store_ps(p0 + p + 8, in); } }; - fp_type* rstate = state.get(); + __m256i idx[1 << L]; + __m256 w[1 << (1 + 2 * H + L)]; - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, w, idx, size, raw_size, rstate); - } + if (CH) { + auto m = GetMasks5(state.num_qubits(), qs, cqs, cvals); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); - void ApplyGate3HHH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; + unsigned k = 3 + H + cqs.size(); + unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + uint64_t size = uint64_t{1} << n; - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; + for_.Run(size * size2, f, w, m.imaskh, m.qmaskh, + m.cvalsh, idx, size, raw_size, state.get()); + } else { + auto m = GetMasks6(state.num_qubits(), qs, cqs, cvals); + FillPermutationIndices(m.qmaskl, idx); + FillControlledMatrixL( + m.cvalsl, m.cmaskl, m.qmaskl, matrix, (fp_type*) w); + + unsigned k = 3 + H + cqs.size() - m.cl; + unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + uint64_t size = uint64_t{1} << n; + + for_.Run(size * size2, f, w, m.imaskh, m.qmaskh, + m.cvalsh, idx, size, raw_size, state.get()); } + } + +#else // __BMI2__ + template + void ApplyGateH(const std::vector& qs, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { + const uint64_t* ms, const uint64_t* xss, uint64_t size, + uint64_t row_size, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m256 ru, iu, rn, in; - __m256 rs[8], is[8]; + __m256 rs[hsize], is[hsize]; - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]) | (32 * ii & ms[2]) - | (64 * ii & ms[3]); + uint64_t r = 8 * (i % size); + uint64_t s = i / size; + + uint64_t t = r & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + r *= 2; + t |= r & ms[j]; + } - auto p0 = rstate + row_size * r + 2 * c; + auto p0 = rstate + row_size * s + 2 * t; - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = _mm256_load_ps(p0 + xss[k]); + is[k] = _mm256_load_ps(p0 + xss[k] + 8); } uint64_t j = 0; - for (unsigned l = 0; l < 8; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm256_set1_ps(v[j]); iu = _mm256_set1_ps(v[j + 1]); rn = _mm256_mul_ps(rs[0], ru); @@ -751,119 +650,74 @@ class UnitaryCalculatorAVX final { j += 2; - for (unsigned n = 1; n < 8; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm256_set1_ps(v[j]); iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); + rn = _mm256_fmadd_ps(rs[l], ru, rn); + in = _mm256_fmadd_ps(rs[l], iu, in); + rn = _mm256_fnmadd_ps(is[l], iu, rn); + in = _mm256_fmadd_ps(is[l], ru, in); j += 2; } - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); + _mm256_store_ps(p0 + xss[k], rn); + _mm256_store_ps(p0 + xss[k] + 8, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; - unsigned k = 6; + FillIndices(state.num_qubits(), qs, ms, xss); + + unsigned k = 3 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); + for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, state.get()); } - void ApplyGate3HHL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[1]; - - auto s = UnitarySpace::Create(4); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } + template + void ApplyGateL(const std::vector& qs, + const fp_type* matrix, State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, + const uint64_t* ms, const uint64_t* xss, const __m256i* idx, + uint64_t size, uint64_t row_size, fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; - unsigned l = 2 * (8 * i + m); + __m256 rn, in; + __m256 rs[gsize], is[gsize]; - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } + uint64_t r = 8 * (i % size); + uint64_t s = i / size; - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } + uint64_t t = r & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + r *= 2; + t |= r & ms[j]; } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]) | (32 * ii & ms[2]); + auto p0 = rstate + row_size * s + 2 * t; - auto p0 = rstate + row_size * r + 2 * c; + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; + rs[k2] = _mm256_load_ps(p0 + xss[k]); + is[k2] = _mm256_load_ps(p0 + xss[k] + 8); - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm256_permutevar8x32_ps(rs[k2], idx[l - 1]); + is[k2 + l] = _mm256_permutevar8x32_ps(is[k2], idx[l - 1]); } } uint64_t j = 0; - for (unsigned l = 0; l < 4; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm256_mul_ps(rs[0], w[j]); in = _mm256_mul_ps(rs[0], w[j + 1]); rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); @@ -871,206 +725,149 @@ class UnitaryCalculatorAVX final { j += 2; - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm256_fmadd_ps(rs[l], w[j], rn); + in = _mm256_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm256_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm256_fmadd_ps(is[l], w[j], in); j += 2; } - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); + _mm256_store_ps(p0 + xss[k], rn); + _mm256_store_ps(p0 + xss[k] + 8, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + __m256i idx[1 << L]; + __m256 w[1 << (1 + 2 * H + L)]; + + auto m = GetMasks11(qs); - unsigned k = 5; + FillIndices(state.num_qubits(), qs, ms, xss); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); + + unsigned k = 3 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); + for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, state.get()); } - void ApplyGate3HLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[3]; - - auto s = UnitarySpace::Create(4); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } + template + void ApplyControlledGateHH(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, + const uint64_t* ms, const uint64_t* xss, uint64_t cvalsh, + uint64_t cmaskh, uint64_t size, uint64_t row_size, + fp_type* rstate) { + constexpr unsigned hsize = 1 << H; - unsigned l = 2 * (8 * i + m); + __m256 ru, iu, rn, in; + __m256 rs[hsize], is[hsize]; - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } + uint64_t r = 8 * (i % size); + uint64_t s = i / size; - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } + uint64_t t = r & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + r *= 2; + t |= r & ms[j]; } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]); + if ((t & cmaskh) != cvalsh) return; - auto p0 = rstate + row_size * r + 2 * c; + auto p0 = rstate + row_size * s + 2 * t; - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = _mm256_load_ps(p0 + xss[k]); + is[k] = _mm256_load_ps(p0 + xss[k] + 8); } uint64_t j = 0; - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); + for (unsigned k = 0; k < hsize; ++k) { + ru = _mm256_set1_ps(v[j]); + iu = _mm256_set1_ps(v[j + 1]); + rn = _mm256_mul_ps(rs[0], ru); + in = _mm256_mul_ps(rs[0], iu); + rn = _mm256_fnmadd_ps(is[0], iu, rn); + in = _mm256_fmadd_ps(is[0], ru, in); j += 2; - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < hsize; ++l) { + ru = _mm256_set1_ps(v[j]); + iu = _mm256_set1_ps(v[j + 1]); + rn = _mm256_fmadd_ps(rs[l], ru, rn); + in = _mm256_fmadd_ps(rs[l], iu, in); + rn = _mm256_fnmadd_ps(is[l], iu, rn); + in = _mm256_fmadd_ps(is[l], ru, in); j += 2; } - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); + _mm256_store_ps(p0 + xss[k], rn); + _mm256_store_ps(p0 + xss[k] + 8, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; - unsigned k = 4; + auto m = GetMasks7(state.num_qubits(), qs, cqs, cvals); + FillIndices(state.num_qubits(), qs, ms, xss); + + unsigned k = 3 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); + for_.Run(size * size2, f, + matrix, ms, xss, m.cvalsh, m.cmaskh, size, raw_size, state.get()); } - void ApplyGate3LLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - unsigned p[8]; - __m256i idx[7]; - - auto s = UnitarySpace::Create(3); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 8 * k + 8 * (m / 8) + (k + m) % 8); - } + template + void ApplyControlledGateHL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, + const uint64_t* ms, const uint64_t* xss, uint64_t cvalsh, + uint64_t cmaskh, uint64_t size, uint64_t row_size, + fp_type* rstate) { + constexpr unsigned hsize = 1 << H; - unsigned l = 2 * (8 * i + m); + __m256 rn, in; + __m256 rs[hsize], is[hsize]; - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } + uint64_t r = 8 * (i % size); + uint64_t s = i / size; - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } + uint64_t t = r & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + r *= 2; + t |= r & ms[j]; } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - uint64_t ii = i % size; - uint64_t r = i / size; - auto p0 = rstate + row_size * r + 16 * ii; + if ((t & cmaskh) != cvalsh) return; - for (unsigned l = 0; l < 1; ++l) { - rs[8 * l] = _mm256_load_ps(p0); - is[8 * l] = _mm256_load_ps(p0 + 8); + auto p0 = rstate + row_size * s + 2 * t; - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = _mm256_load_ps(p0 + xss[k]); + is[k] = _mm256_load_ps(p0 + xss[k] + 8); } uint64_t j = 0; - for (unsigned l = 0; l < 1; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm256_mul_ps(rs[0], w[j]); in = _mm256_mul_ps(rs[0], w[j + 1]); rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); @@ -1078,4650 +875,148 @@ class UnitaryCalculatorAVX final { j += 2; - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < hsize; ++l) { + rn = _mm256_fmadd_ps(rs[l], w[j], rn); + in = _mm256_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm256_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm256_fmadd_ps(is[l], w[j], in); j += 2; } - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); + _mm256_store_ps(p0 + xss[k], rn); + _mm256_store_ps(p0 + xss[k] + 8, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + __m256 w[1 << (1 + 2 * H)]; + + auto m = GetMasks8<3>(state.num_qubits(), qs, cqs, cvals); + FillIndices(state.num_qubits(), qs, ms, xss); + FillControlledMatrixH(m.cvalsl, m.cmaskl, matrix, (fp_type*) w); - unsigned k = 3; + unsigned k = 3 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, w, idx, size, raw_size, rstate); + for_.Run(size * size2, f, + w, ms, xss, m.cvalsh, m.cmaskh, size, raw_size, state.get()); } - void ApplyGate4HHHH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; + template + void ApplyControlledGateL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, + const uint64_t* ms, const uint64_t* xss, uint64_t cvalsh, + uint64_t cmaskh, const __m256i* idx, uint64_t size, + uint64_t row_size, fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } + __m256 rn, in; + __m256 rs[gsize], is[gsize]; + + uint64_t r = 8 * (i % size); + uint64_t s = i / size; + + uint64_t t = r & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + r *= 2; + t |= r & ms[j]; } - xss[i] = a; - } - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[16], is[16]; + if ((t & cmaskh) != cvalsh) return; - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]) | (32 * ii & ms[2]) - | (64 * ii & ms[3]) | (128 * ii & ms[4]); + auto p0 = rstate + row_size * s + 2 * t; - auto p0 = rstate + row_size * r + 2 * c; + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); + rs[k2] = _mm256_load_ps(p0 + xss[k]); + is[k2] = _mm256_load_ps(p0 + xss[k] + 8); + + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm256_permutevar8x32_ps(rs[k2], idx[l - 1]); + is[k2 + l] = _mm256_permutevar8x32_ps(is[k2], idx[l - 1]); + } } uint64_t j = 0; - for (unsigned l = 0; l < 16; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); + for (unsigned k = 0; k < hsize; ++k) { + rn = _mm256_mul_ps(rs[0], w[j]); + in = _mm256_mul_ps(rs[0], w[j + 1]); + rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); + in = _mm256_fmadd_ps(is[0], w[j], in); j += 2; - for (unsigned n = 1; n < 16; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm256_fmadd_ps(rs[l], w[j], rn); + in = _mm256_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm256_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm256_fmadd_ps(is[l], w[j], in); j += 2; } - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); + _mm256_store_ps(p0 + xss[k], rn); + _mm256_store_ps(p0 + xss[k] + 8, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + __m256i idx[1 << L]; + __m256 w[1 << (1 + 2 * H + L)]; - unsigned k = 7; + FillIndices(state.num_qubits(), qs, ms, xss); + + unsigned k = 3 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); - } + if (CH) { + auto m = GetMasks9(state.num_qubits(), qs, cqs, cvals); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); - void ApplyGate4HHHL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; + for_.Run(size * size2, f, w, ms, xss, m.cvalsh, + m.cmaskh, idx, size, raw_size, state.get()); + } else { + auto m = GetMasks10(state.num_qubits(), qs, cqs, cvals); + FillPermutationIndices(m.qmaskl, idx); + FillControlledMatrixL( + m.cvalsl, m.cmaskl, m.qmaskl, matrix, (fp_type*) w); - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; + for_.Run(size * size2, f, w, ms, xss, m.cvalsh, + m.cmaskh, idx, size, raw_size, state.get()); } + } - unsigned p[8]; - __m256i idx[1]; +#endif // __BMI2__ - auto s = UnitarySpace::Create(5); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; + template + static void FillPermutationIndices(unsigned qmaskl, __m256i* idx) { + constexpr unsigned lsize = 1 << L; - unsigned qmask = (1 << qs[0]); + for (unsigned i = 0; i < lsize - 1; ++i) { + unsigned p[8]; - for (unsigned i = 0; i < 1; ++i) { for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); + p[j] = MaskedAdd<3>(j, i + 1, qmaskl, lsize) | (j & (-1 ^ qmaskl)); } idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]) | (32 * ii & ms[2]) - | (64 * ii & ms[3]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate4HHLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[3]; - - auto s = UnitarySpace::Create(5); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]) | (32 * ii & ms[2]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate4HLLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[7]; - - auto s = UnitarySpace::Create(4); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (128 * i + 16 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[8 * l] = _mm256_load_ps(p0 + xss[l]); - is[8 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate5HHHHH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[32], is[32]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]) | (32 * ii & ms[2]) - | (64 * ii & ms[3]) | (128 * ii & ms[4]) | (256 * ii & ms[5]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 32; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); - } - - void ApplyGate5HHHHL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[1]; - - auto s = UnitarySpace::Create(6); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 32 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[32], is[32]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]) | (32 * ii & ms[2]) - | (64 * ii & ms[3]) | (128 * ii & ms[4]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate5HHHLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[3]; - - auto s = UnitarySpace::Create(6); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (128 * i + 32 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[32], is[32]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]) | (32 * ii & ms[2]) - | (64 * ii & ms[3]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate5HHLLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 3] + 1); - ms[i] = ((uint64_t{1} << qs[i + 3]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[7]; - - auto s = UnitarySpace::Create(5); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (256 * i + 32 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[32], is[32]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]) | (32 * ii & ms[2]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[8 * l] = _mm256_load_ps(p0 + xss[l]); - is[8 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate6HHHHHH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[6]; - uint64_t ms[7]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 6; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[6] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[5] - 1); - - uint64_t xss[64]; - for (unsigned i = 0; i < 64; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 6; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[64], is[64]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]) | (32 * ii & ms[2]) - | (64 * ii & ms[3]) | (128 * ii & ms[4]) | (256 * ii & ms[5]) - | (512 * ii & ms[6]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 64; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 64; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 9; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); - } - - void ApplyGate6HHHHHL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[1]; - - auto s = UnitarySpace::Create(7); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 32; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (128 * i + 64 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[64], is[64]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]) | (32 * ii & ms[2]) - | (64 * ii & ms[3]) | (128 * ii & ms[4]) | (256 * ii & ms[5]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 32; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate6HHHHLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[3]; - - auto s = UnitarySpace::Create(7); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (256 * i + 64 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[64], is[64]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]) | (32 * ii & ms[2]) - | (64 * ii & ms[3]) | (128 * ii & ms[4]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate6HHHLLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 3] + 1); - ms[i] = ((uint64_t{1} << qs[i + 3]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[8]; - __m256i idx[7]; - - auto s = UnitarySpace::Create(6); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (512 * i + 64 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[64], is[64]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (8 * ii & ms[0]) | (16 * ii & ms[1]) | (32 * ii & ms[2]) - | (64 * ii & ms[3]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[8 * l] = _mm256_load_ps(p0 + xss[l]); - is[8 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyControlledGate1H_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[2], is[2]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate1H_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - - auto s = UnitarySpace::Create(3); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (2 * i + 2 * k + m); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 2 == (p[j] / 2) % 2 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m256 rn, in; - __m256 rs[2], is[2]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate1L_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = UnitarySpace::Create(2); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[2], is[2]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm256_load_ps(p0); - is[2 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate1L_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = UnitarySpace::Create(2); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 2 == (p[j] / 2) % 2 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[2], is[2]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm256_load_ps(p0); - is[2 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate2HH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate2HH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - - auto s = UnitarySpace::Create(4); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (4 * i + 4 * k + m); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m256 rn, in; - __m256 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate2HL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = UnitarySpace::Create(3); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate2HL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = UnitarySpace::Create(3); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate2LL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[3]; - - auto s = UnitarySpace::Create(3); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm256_load_ps(p0); - is[4 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate2LL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[3]; - - auto s = UnitarySpace::Create(3); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm256_load_ps(p0); - is[4 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate3HHH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate3HHH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - - auto s = UnitarySpace::Create(5); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (8 * i + 8 * k + m); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate3HHL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = UnitarySpace::Create(4); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate3HHL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = UnitarySpace::Create(4); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate3HLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[3]; - - auto s = UnitarySpace::Create(4); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate3HLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[3]; - - auto s = UnitarySpace::Create(4); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate3LLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[7]; - - auto s = UnitarySpace::Create(3); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 8 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[8 * l] = _mm256_load_ps(p0); - is[8 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate3LLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[7]; - - auto s = UnitarySpace::Create(3); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 8 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[8 * l] = _mm256_load_ps(p0); - is[8 * l] = _mm256_load_ps(p0 + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0, rn); - _mm256_store_ps(p0 + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate4HHHH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m256 ru, iu, rn, in; - __m256 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_mul_ps(rs[0], ru); - in = _mm256_mul_ps(rs[0], iu); - rn = _mm256_fnmadd_ps(is[0], iu, rn); - in = _mm256_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - ru = _mm256_set1_ps(v[j]); - iu = _mm256_set1_ps(v[j + 1]); - rn = _mm256_fmadd_ps(rs[n], ru, rn); - in = _mm256_fmadd_ps(rs[n], iu, in); - rn = _mm256_fnmadd_ps(is[n], iu, rn); - in = _mm256_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate4HHHH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - - auto s = UnitarySpace::Create(6); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (16 * i + 16 * k + m); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm256_load_ps(p0 + xss[l]); - is[l] = _mm256_load_ps(p0 + xss[l] + 8); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate4HHHL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = UnitarySpace::Create(5); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate4HHHL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[1]; - - auto s = UnitarySpace::Create(5); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm256_load_ps(p0 + xss[l]); - is[2 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm256_permutevar8x32_ps(rs[2 * l], idx[j - 1]); - is[2 * l + j] = _mm256_permutevar8x32_ps(is[2 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate4HHLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[3]; - - auto s = UnitarySpace::Create(5); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate4HHLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[3]; - - auto s = UnitarySpace::Create(5); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm256_load_ps(p0 + xss[l]); - is[4 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm256_permutevar8x32_ps(rs[4 * l], idx[j - 1]); - is[4 * l + j] = _mm256_permutevar8x32_ps(is[4 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate4HLLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[7]; - - auto s = UnitarySpace::Create(4); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (128 * i + 16 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[8 * l] = _mm256_load_ps(p0 + xss[l]); - is[8 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate4HLLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 3, emaskl); - - for (auto q : qs) { - if (q > 2) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 7; - - unsigned p[8]; - __m256i idx[7]; - - auto s = UnitarySpace::Create(4); - __m256* w = (__m256*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 8; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm256_set_epi32(p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 8; ++j) { - unsigned k = bits::CompressBits(j, 3, qmask); - p[j] = 2 * (128 * i + 16 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 8; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[8 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 8; ++j) { - wf[8 * l + j + 8] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m256* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m256i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m256 rn, in; - __m256 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[8 * l] = _mm256_load_ps(p0 + xss[l]); - is[8 * l] = _mm256_load_ps(p0 + xss[l] + 8); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm256_permutevar8x32_ps(rs[8 * l], idx[j - 1]); - is[8 * l + j] = _mm256_permutevar8x32_ps(is[8 * l], idx[j - 1]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm256_mul_ps(rs[0], w[j]); - in = _mm256_mul_ps(rs[0], w[j + 1]); - rn = _mm256_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm256_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm256_fmadd_ps(rs[n], w[j], rn); - in = _mm256_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm256_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm256_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm256_store_ps(p0 + xss[l], rn); - _mm256_store_ps(p0 + xss[l] + 8, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - static unsigned MaskedAdd( - unsigned a, unsigned b, unsigned mask, unsigned lsize) { - unsigned c = bits::CompressBits(a, 3, mask); - return bits::ExpandBits((c + b) % lsize, 3, mask); } For for_; diff --git a/lib/unitary_calculator_avx512.h b/lib/unitary_calculator_avx512.h index 57ba6484..4020e313 100644 --- a/lib/unitary_calculator_avx512.h +++ b/lib/unitary_calculator_avx512.h @@ -17,21 +17,22 @@ #include -#include #include #include +#include +#include -#include "bits.h" +#include "simulator.h" #include "unitaryspace_avx512.h" namespace qsim { namespace unitary { /** - * Quantum circuit unitary calculator with AVX512 vectorization. + * Quantum circuit simulator with AVX512 vectorization. */ template -class UnitaryCalculatorAVX512 final { +class UnitaryCalculatorAVX512 final : public SimulatorBase { public: using UnitarySpace = UnitarySpaceAVX512; using Unitary = typename UnitarySpace::Unitary; @@ -50,74 +51,74 @@ class UnitaryCalculatorAVX512 final { * @param state The state of the system, to be updated by this method. */ void ApplyGate(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { + const fp_type* matrix, State& state) const { // Assume qs[0] < qs[1] < qs[2] < ... . switch (qs.size()) { case 1: if (qs[0] > 3) { - ApplyGate1H(qs, matrix, state); + ApplyGateH<1>(qs, matrix, state); } else { - ApplyGate1L(qs, matrix, state); + ApplyGateL<0, 1>(qs, matrix, state); } break; case 2: if (qs[0] > 3) { - ApplyGate2HH(qs, matrix, state); + ApplyGateH<2>(qs, matrix, state); } else if (qs[1] > 3) { - ApplyGate2HL(qs, matrix, state); + ApplyGateL<1, 1>(qs, matrix, state); } else { - ApplyGate2LL(qs, matrix, state); + ApplyGateL<0, 2>(qs, matrix, state); } break; case 3: if (qs[0] > 3) { - ApplyGate3HHH(qs, matrix, state); + ApplyGateH<3>(qs, matrix, state); } else if (qs[1] > 3) { - ApplyGate3HHL(qs, matrix, state); + ApplyGateL<2, 1>(qs, matrix, state); } else if (qs[2] > 3) { - ApplyGate3HLL(qs, matrix, state); + ApplyGateL<1, 2>(qs, matrix, state); } else { - ApplyGate3LLL(qs, matrix, state); + ApplyGateL<0, 3>(qs, matrix, state); } break; case 4: if (qs[0] > 3) { - ApplyGate4HHHH(qs, matrix, state); + ApplyGateH<4>(qs, matrix, state); } else if (qs[1] > 3) { - ApplyGate4HHHL(qs, matrix, state); + ApplyGateL<3, 1>(qs, matrix, state); } else if (qs[2] > 3) { - ApplyGate4HHLL(qs, matrix, state); + ApplyGateL<2, 2>(qs, matrix, state); } else if (qs[3] > 3) { - ApplyGate4HLLL(qs, matrix, state); + ApplyGateL<1, 3>(qs, matrix, state); } else { - ApplyGate4LLLL(qs, matrix, state); + ApplyGateL<0, 4>(qs, matrix, state); } break; case 5: if (qs[0] > 3) { - ApplyGate5HHHHH(qs, matrix, state); + ApplyGateH<5>(qs, matrix, state); } else if (qs[1] > 3) { - ApplyGate5HHHHL(qs, matrix, state); + ApplyGateL<4, 1>(qs, matrix, state); } else if (qs[2] > 3) { - ApplyGate5HHHLL(qs, matrix, state); + ApplyGateL<3, 2>(qs, matrix, state); } else if (qs[3] > 3) { - ApplyGate5HHLLL(qs, matrix, state); + ApplyGateL<2, 3>(qs, matrix, state); } else { - ApplyGate5HLLLL(qs, matrix, state); + ApplyGateL<1, 4>(qs, matrix, state); } break; case 6: if (qs[0] > 3) { - ApplyGate6HHHHHH(qs, matrix, state); + ApplyGateH<6>(qs, matrix, state); } else if (qs[1] > 3) { - ApplyGate6HHHHHL(qs, matrix, state); + ApplyGateL<5, 1>(qs, matrix, state); } else if (qs[2] > 3) { - ApplyGate6HHHHLL(qs, matrix, state); + ApplyGateL<4, 2>(qs, matrix, state); } else if (qs[3] > 3) { - ApplyGate6HHHLLL(qs, matrix, state); + ApplyGateL<3, 3>(qs, matrix, state); } else { - ApplyGate6HHLLLL(qs, matrix, state); + ApplyGateL<2, 4>(qs, matrix, state); } break; default: @@ -130,13 +131,16 @@ class UnitaryCalculatorAVX512 final { * Applies a controlled gate using AVX512 instructions. * @param qs Indices of the qubits affected by this gate. * @param cqs Indices of control qubits. - * @param cmask Bit mask of control qubit values. + * @param cvals Bit mask of control qubit values. * @param matrix Matrix representation of the gate to be applied. * @param state The state of the system, to be updated by this method. */ void ApplyControlledGate(const std::vector& qs, - const std::vector& cqs, uint64_t cmask, - const fp_type* matrix, Unitary& state) const { + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { + // Assume qs[0] < qs[1] < qs[2] < ... . + // Assume cqs[0] < cqs[1] < cqs[2] < ... . + if (cqs.size() == 0) { ApplyGate(qs, matrix, state); return; @@ -146,96 +150,96 @@ class UnitaryCalculatorAVX512 final { case 1: if (qs[0] > 3) { if (cqs[0] > 3) { - ApplyControlledGate1H_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate1H_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<1>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 3) { - ApplyControlledGate1L_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate1L_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 1, 0>(qs, cqs, cvals, matrix, state); } } break; case 2: if (qs[0] > 3) { if (cqs[0] > 3) { - ApplyControlledGate2HH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<2>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2HH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<2>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 3) { if (cqs[0] > 3) { - ApplyControlledGate2HL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2HL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 1, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 3) { - ApplyControlledGate2LL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2LL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 2, 0>(qs, cqs, cvals, matrix, state); } } break; case 3: if (qs[0] > 3) { if (cqs[0] > 3) { - ApplyControlledGate3HHH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<3>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HHH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<3>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 3) { if (cqs[0] > 3) { - ApplyControlledGate3HHL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HHL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 1, 0>(qs, cqs, cvals, matrix, state); } } else if (qs[2] > 3) { if (cqs[0] > 3) { - ApplyControlledGate3HLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 2, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 3) { - ApplyControlledGate3LLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 3, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3LLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 3, 0>(qs, cqs, cvals, matrix, state); } } break; case 4: if (qs[0] > 3) { if (cqs[0] > 3) { - ApplyControlledGate4HHHH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<4>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHHH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<4>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 3) { if (cqs[0] > 3) { - ApplyControlledGate4HHHL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<3, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHHL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<3, 1, 0>(qs, cqs, cvals, matrix, state); } } else if (qs[2] > 3) { if (cqs[0] > 3) { - ApplyControlledGate4HHLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 2, 0>(qs, cqs, cvals, matrix, state); } } else if (qs[3] > 3) { if (cqs[0] > 3) { - ApplyControlledGate4HLLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 3, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HLLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 3, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 3) { - ApplyControlledGate4LLLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 4, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4LLLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 4, 0>(qs, cqs, cvals, matrix, state); } } break; @@ -253,46 +257,32 @@ class UnitaryCalculatorAVX512 final { } private: - void ApplyGate1H(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - + template + void ApplyGateH(const std::vector& qs, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { + uint64_t imaskh, uint64_t qmaskh, uint64_t size, + uint64_t row_size, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m512 ru, iu, rn, in; - __m512 rs[2], is[2]; + __m512 rs[hsize], is[hsize]; - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]); + uint64_t r = i % size; + uint64_t s = i / size; - auto p0 = rstate + row_size * r + 2 * c; + auto p0 = rstate + row_size * s + _pdep_u64(r, imaskh); - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); + for (unsigned k = 0; k < hsize; ++k) { + uint64_t p = _pdep_u64(k, qmaskh); + + rs[k] = _mm512_load_ps(p0 + p); + is[k] = _mm512_load_ps(p0 + p + 16); } uint64_t j = 0; - for (unsigned l = 0; l < 2; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm512_set1_ps(v[j]); iu = _mm512_set1_ps(v[j + 1]); rn = _mm512_mul_ps(rs[0], ru); @@ -302,96 +292,70 @@ class UnitaryCalculatorAVX512 final { j += 2; - for (unsigned n = 1; n < 2; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm512_set1_ps(v[j]); iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); + rn = _mm512_fmadd_ps(rs[l], ru, rn); + in = _mm512_fmadd_ps(rs[l], iu, in); + rn = _mm512_fnmadd_ps(is[l], iu, rn); + in = _mm512_fmadd_ps(is[l], ru, in); j += 2; } - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm512_store_ps(p0 + p, rn); + _mm512_store_ps(p0 + p + 16, in); } }; - fp_type* rstate = state.get(); + auto m = GetMasks1(qs); - unsigned k = 5; + unsigned k = 4 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); + for_.Run(size * size2, f, + matrix, m.imaskh, m.qmaskh, size, raw_size, state.get()); } - void ApplyGate1L(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - unsigned p[16]; - __m512i idx[1]; - - auto s = UnitarySpace::Create(3); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); + template + void ApplyGateL(const std::vector& qs, + const fp_type* matrix, State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, + uint64_t imaskh, uint64_t qmaskh, const __m512i* idx, + uint64_t size, uint64_t row_size, fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } + __m512 rn, in; + __m512 rs[gsize], is[gsize]; - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } + uint64_t r = i % size; + uint64_t s = i / size; - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[2], is[2]; + auto p0 = rstate + row_size * s + _pdep_u64(r, imaskh); - uint64_t ii = i % size; - uint64_t r = i / size; - auto p0 = rstate + row_size * r + 32 * ii; + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm512_load_ps(p0); - is[2 * l] = _mm512_load_ps(p0 + 16); + rs[k2] = _mm512_load_ps(p0 + p); + is[k2] = _mm512_load_ps(p0 + p + 16); - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm512_permutexvar_ps(idx[l - 1], rs[k2]); + is[k2 + l] = _mm512_permutexvar_ps(idx[l - 1], is[k2]); } } uint64_t j = 0; - for (unsigned l = 0; l < 1; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm512_mul_ps(rs[0], w[j]); in = _mm512_mul_ps(rs[0], w[j + 1]); rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); @@ -399,75 +363,66 @@ class UnitaryCalculatorAVX512 final { j += 2; - for (unsigned n = 1; n < 2; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm512_fmadd_ps(rs[l], w[j], rn); + in = _mm512_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm512_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm512_fmadd_ps(is[l], w[j], in); j += 2; } - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm512_store_ps(p0 + p, rn); + _mm512_store_ps(p0 + p + 16, in); } }; - fp_type* rstate = state.get(); + __m512i idx[1 << L]; + __m512 w[1 << (1 + 2 * H + L)]; + + auto m = GetMasks2(qs); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); - unsigned k = 4; + unsigned k = 4 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, w, idx, size, raw_size, rstate); + for_.Run(size * size2, f, + w, m.imaskh, m.qmaskh, idx, size, raw_size, state.get()); } - void ApplyGate2HH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - + template + void ApplyControlledGateHH(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, + uint64_t imaskh, uint64_t qmaskh, uint64_t cvalsh, uint64_t size, uint64_t row_size, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m512 ru, iu, rn, in; - __m512 rs[4], is[4]; + __m512 rs[hsize], is[hsize]; - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]); + uint64_t r = i % size; + uint64_t s = i / size; - auto p0 = rstate + row_size * r + 2 * c; + auto p0 = rstate + row_size * s + (_pdep_u64(r, imaskh) | cvalsh); - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); + for (unsigned k = 0; k < hsize; ++k) { + uint64_t p = _pdep_u64(k, qmaskh); + + rs[k] = _mm512_load_ps(p0 + p); + is[k] = _mm512_load_ps(p0 + p + 16); } uint64_t j = 0; - for (unsigned l = 0; l < 4; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm512_set1_ps(v[j]); iu = _mm512_set1_ps(v[j + 1]); rn = _mm512_mul_ps(rs[0], ru); @@ -477,117 +432,63 @@ class UnitaryCalculatorAVX512 final { j += 2; - for (unsigned n = 1; n < 4; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm512_set1_ps(v[j]); iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); + rn = _mm512_fmadd_ps(rs[l], ru, rn); + in = _mm512_fmadd_ps(rs[l], iu, in); + rn = _mm512_fnmadd_ps(is[l], iu, rn); + in = _mm512_fmadd_ps(is[l], ru, in); j += 2; } - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm512_store_ps(p0 + p, rn); + _mm512_store_ps(p0 + p + 16, in); } }; - fp_type* rstate = state.get(); + auto m = GetMasks3(state.num_qubits(), qs, cqs, cvals); - unsigned k = 6; + unsigned k = 4 + H + cqs.size(); unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); + for_.Run(size * size2, f, + matrix, m.imaskh, m.qmaskh, m.cvalsh, size, raw_size, state.get()); } - void ApplyGate2HL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = UnitarySpace::Create(4); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - + template + void ApplyControlledGateHL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { + uint64_t imaskh, uint64_t qmaskh, uint64_t cvalsh, + uint64_t size, uint64_t row_size, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m512 rn, in; - __m512 rs[4], is[4]; + __m512 rs[hsize], is[hsize]; - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]); + uint64_t r = i % size; + uint64_t s = i / size; - auto p0 = rstate + row_size * r + 2 * c; + auto p0 = rstate + row_size * s + (_pdep_u64(r, imaskh) | cvalsh); - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); + for (unsigned k = 0; k < hsize; ++k) { + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } + rs[k] = _mm512_load_ps(p0 + p); + is[k] = _mm512_load_ps(p0 + p + 16); } uint64_t j = 0; - for (unsigned l = 0; l < 2; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm512_mul_ps(rs[0], w[j]); in = _mm512_mul_ps(rs[0], w[j + 1]); rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); @@ -595,295 +496,73 @@ class UnitaryCalculatorAVX512 final { j += 2; - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < hsize; ++l) { + rn = _mm512_fmadd_ps(rs[l], w[j], rn); + in = _mm512_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm512_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm512_fmadd_ps(is[l], w[j], in); j += 2; } - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate2LL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - unsigned p[16]; - __m512i idx[3]; - - auto s = UnitarySpace::Create(3); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - auto p0 = rstate + row_size * r + 32 * ii; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm512_load_ps(p0); - is[4 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } + uint64_t p = _pdep_u64(k, qmaskh); - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); + _mm512_store_ps(p0 + p, rn); + _mm512_store_ps(p0 + p + 16, in); } }; - fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, idx, size, raw_size, rstate); - } - - void ApplyGate3HHH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]) - | (128 * ii & ms[3]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; + __m512 w[1 << (1 + 2 * H)]; - fp_type* rstate = state.get(); + auto m = GetMasks4(state.num_qubits(), qs, cqs, cvals); + FillControlledMatrixH(m.cvalsl, m.cmaskl, matrix, (fp_type*) w); - unsigned k = 7; + unsigned k = 4 + H + cqs.size() - m.cl; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); + for_.Run(size * size2, f, + w, m.imaskh, m.qmaskh, m.cvalsh, size, raw_size, state.get()); } - void ApplyGate3HHL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = UnitarySpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - + template + void ApplyControlledGateL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, + uint64_t imaskh, uint64_t qmaskh, uint64_t cvalsh, const __m512i* idx, uint64_t size, uint64_t row_size, fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; + __m512 rn, in; - __m512 rs[8], is[8]; + __m512 rs[gsize], is[gsize]; - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]); + uint64_t r = i % size; + uint64_t s = i / size; - auto p0 = rstate + row_size * r + 2 * c; + auto p0 = rstate + row_size * s + (_pdep_u64(r, imaskh) | cvalsh); - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; + uint64_t p = _pdep_u64(k, qmaskh); - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); + rs[k2] = _mm512_load_ps(p0 + p); + is[k2] = _mm512_load_ps(p0 + p + 16); + + for (unsigned l = 1; l < lsize; ++l) { + rs[k2 + l] = _mm512_permutexvar_ps(idx[l - 1], rs[k2]); + is[k2 + l] = _mm512_permutexvar_ps(idx[l - 1], is[k2]); } } uint64_t j = 0; - for (unsigned l = 0; l < 4; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm512_mul_ps(rs[0], w[j]); in = _mm512_mul_ps(rs[0], w[j + 1]); rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); @@ -891,5489 +570,69 @@ class UnitaryCalculatorAVX512 final { j += 2; - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm512_fmadd_ps(rs[l], w[j], rn); + in = _mm512_fmadd_ps(rs[l], w[j + 1], in); + rn = _mm512_fnmadd_ps(is[l], w[j + 1], rn); + in = _mm512_fmadd_ps(is[l], w[j], in); j += 2; } - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); + uint64_t p = _pdep_u64(k, qmaskh); + + _mm512_store_ps(p0 + p, rn); + _mm512_store_ps(p0 + p + 16, in); } }; - fp_type* rstate = state.get(); + __m512i idx[1 << L]; + __m512 w[1 << (1 + 2 * H + L)]; - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } + if (CH) { + auto m = GetMasks5(state.num_qubits(), qs, cqs, cvals); + FillPermutationIndices(m.qmaskl, idx); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); - void ApplyGate3HLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; + unsigned k = 4 + H + cqs.size(); + unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + uint64_t size = uint64_t{1} << n; - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); + for_.Run(size * size2, f, w, m.imaskh, m.qmaskh, + m.cvalsh, idx, size, raw_size, state.get()); + } else { + auto m = GetMasks6(state.num_qubits(), qs, cqs, cvals); + FillPermutationIndices(m.qmaskl, idx); + FillControlledMatrixL( + m.cvalsl, m.cmaskl, m.qmaskl, matrix, (fp_type*) w); - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } + unsigned k = 4 + H + cqs.size() - m.cl; + unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + uint64_t size = uint64_t{1} << n; - unsigned p[16]; - __m512i idx[3]; + for_.Run(size * size2, f, w, m.imaskh, m.qmaskh, + m.cvalsh, idx, size, raw_size, state.get()); + } + } - auto s = UnitarySpace::Create(4); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; + template + static void FillPermutationIndices(unsigned qmaskl, __m512i* idx) { + constexpr unsigned lsize = 1 << L; - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); + for (unsigned i = 0; i < lsize; ++i) { + unsigned p[16]; - for (unsigned i = 0; i < 3; ++i) { for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); + p[j] = MaskedAdd<4>(j, i + 1, qmaskl, lsize) | (j & (-1 ^ qmaskl)); } idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], p[9], p[8], p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]); } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate3LLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - unsigned p[16]; - __m512i idx[7]; - - auto s = UnitarySpace::Create(4); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 8 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - auto p0 = rstate + row_size * r + 32 * ii; - - for (unsigned l = 0; l < 1; ++l) { - rs[8 * l] = _mm512_load_ps(p0); - is[8 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, idx, size, raw_size, rstate); - } - - void ApplyGate4HHHH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]) - | (128 * ii & ms[3]) | (256 * ii & ms[4]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); - } - - void ApplyGate4HHHL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = UnitarySpace::Create(6); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]) - | (128 * ii & ms[3]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate4HHLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[3]; - - auto s = UnitarySpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate4HLLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[7]; - - auto s = UnitarySpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (128 * i + 16 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[8 * l] = _mm512_load_ps(p0 + xss[l]); - is[8 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate4LLLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - unsigned p[16]; - __m512i idx[15]; - - auto s = UnitarySpace::Create(4); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]) | (1 << qs[3]); - - for (unsigned i = 0; i < 15; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 16) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (256 * i + 16 * k + 16 * (m / 16) + (k + m) % 16); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - auto p0 = rstate + row_size * r + 32 * ii; - - for (unsigned l = 0; l < 1; ++l) { - rs[16 * l] = _mm512_load_ps(p0); - is[16 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 16; ++j) { - rs[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[16 * l]); - is[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[16 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, idx, size, raw_size, rstate); - } - - void ApplyGate5HHHHH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[32], is[32]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]) - | (128 * ii & ms[3]) | (256 * ii & ms[4]) | (512 * ii & ms[5]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 32; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 9; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); - } - - void ApplyGate5HHHHL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = UnitarySpace::Create(7); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 32 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[32], is[32]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]) - | (128 * ii & ms[3]) | (256 * ii & ms[4]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate5HHHLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[3]; - - auto s = UnitarySpace::Create(6); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (128 * i + 32 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[32], is[32]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]) - | (128 * ii & ms[3]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate5HHLLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 3] + 1); - ms[i] = ((uint64_t{1} << qs[i + 3]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[7]; - - auto s = UnitarySpace::Create(6); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (256 * i + 32 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[32], is[32]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[8 * l] = _mm512_load_ps(p0 + xss[l]); - is[8 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate5HLLLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[4] + 1); - ms[0] = (uint64_t{1} << qs[4]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[15]; - - auto s = UnitarySpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]) | (1 << qs[3]); - - for (unsigned i = 0; i < 15; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 16) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (512 * i + 32 * k + 16 * (m / 16) + (k + m) % 16); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[32], is[32]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[16 * l] = _mm512_load_ps(p0 + xss[l]); - is[16 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 16; ++j) { - rs[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[16 * l]); - is[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[16 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate6HHHHHH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[6]; - uint64_t ms[7]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 6; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[6] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[5] - 1); - - uint64_t xss[64]; - for (unsigned i = 0; i < 64; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 6; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[64], is[64]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]) - | (128 * ii & ms[3]) | (256 * ii & ms[4]) | (512 * ii & ms[5]) - | (1024 * ii & ms[6]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 64; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 64; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 10; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); - } - - void ApplyGate6HHHHHL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[1]; - - auto s = UnitarySpace::Create(8); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 32; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (128 * i + 64 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[64], is[64]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]) - | (128 * ii & ms[3]) | (256 * ii & ms[4]) | (512 * ii & ms[5]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 32; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 9; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate6HHHHLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[3]; - - auto s = UnitarySpace::Create(7); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (256 * i + 64 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[64], is[64]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]) - | (128 * ii & ms[3]) | (256 * ii & ms[4]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate6HHHLLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 3] + 1); - ms[i] = ((uint64_t{1} << qs[i + 3]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[7]; - - auto s = UnitarySpace::Create(7); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (512 * i + 64 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[64], is[64]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]) - | (128 * ii & ms[3]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[8 * l] = _mm512_load_ps(p0 + xss[l]); - is[8 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyGate6HHLLLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[4] + 1); - ms[0] = (uint64_t{1} << qs[4]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 4] + 1); - ms[i] = ((uint64_t{1} << qs[i + 4]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[16]; - __m512i idx[15]; - - auto s = UnitarySpace::Create(6); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]) | (1 << qs[3]); - - for (unsigned i = 0; i < 15; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 16) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (1024 * i + 64 * k + 16 * (m / 16) + (k + m) % 16); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[64], is[64]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (16 * ii & ms[0]) | (32 * ii & ms[1]) | (64 * ii & ms[2]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[16 * l] = _mm512_load_ps(p0 + xss[l]); - is[16 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 16; ++j) { - rs[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[16 * l]); - is[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[16 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, idx, size, raw_size, rstate); - } - - void ApplyControlledGate1H_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[2], is[2]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate1H_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - - auto s = UnitarySpace::Create(3); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (2 * i + 2 * k + m); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 2 == (p[j] / 2) % 2 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m512 rn, in; - __m512 rs[2], is[2]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate1L_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = UnitarySpace::Create(3); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[2], is[2]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm512_load_ps(p0); - is[2 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate1L_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = UnitarySpace::Create(3); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 2 == (p[j] / 2) % 2 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[2], is[2]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm512_load_ps(p0); - is[2 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate2HH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate2HH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - - auto s = UnitarySpace::Create(4); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (4 * i + 4 * k + m); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m512 rn, in; - __m512 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate2HL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = UnitarySpace::Create(4); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate2HL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = UnitarySpace::Create(4); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate2LL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[3]; - - auto s = UnitarySpace::Create(3); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm512_load_ps(p0); - is[4 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate2LL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[3]; - - auto s = UnitarySpace::Create(3); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm512_load_ps(p0); - is[4 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate3HHH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate3HHH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - - auto s = UnitarySpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (8 * i + 8 * k + m); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate3HHL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = UnitarySpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate3HHL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = UnitarySpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate3HLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[3]; - - auto s = UnitarySpace::Create(4); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate3HLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[3]; - - auto s = UnitarySpace::Create(4); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate3LLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[7]; - - auto s = UnitarySpace::Create(4); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 8 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[8 * l] = _mm512_load_ps(p0); - is[8 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate3LLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[7]; - - auto s = UnitarySpace::Create(4); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 8 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[8 * l] = _mm512_load_ps(p0); - is[8 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate4HHHH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m512 ru, iu, rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_mul_ps(rs[0], ru); - in = _mm512_mul_ps(rs[0], iu); - rn = _mm512_fnmadd_ps(is[0], iu, rn); - in = _mm512_fmadd_ps(is[0], ru, in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - ru = _mm512_set1_ps(v[j]); - iu = _mm512_set1_ps(v[j + 1]); - rn = _mm512_fmadd_ps(rs[n], ru, rn); - in = _mm512_fmadd_ps(rs[n], iu, in); - rn = _mm512_fnmadd_ps(is[n], iu, rn); - in = _mm512_fmadd_ps(is[n], ru, in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate4HHHH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - - auto s = UnitarySpace::Create(6); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (16 * i + 16 * k + m); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm512_load_ps(p0 + xss[l]); - is[l] = _mm512_load_ps(p0 + xss[l] + 16); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate4HHHL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = UnitarySpace::Create(6); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate4HHHL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[1]; - - auto s = UnitarySpace::Create(6); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 2) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm512_load_ps(p0 + xss[l]); - is[2 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 2; ++j) { - rs[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[2 * l]); - is[2 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[2 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate4HHLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[3]; - - auto s = UnitarySpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate4HHLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[3]; - - auto s = UnitarySpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 3; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 4) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm512_load_ps(p0 + xss[l]); - is[4 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 4; ++j) { - rs[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[4 * l]); - is[4 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[4 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate4HLLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[7]; - - auto s = UnitarySpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (128 * i + 16 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[8 * l] = _mm512_load_ps(p0 + xss[l]); - is[8 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate4HLLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[3] + 1); - ms[0] = (uint64_t{1} << qs[3]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[7]; - - auto s = UnitarySpace::Create(5); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]); - - for (unsigned i = 0; i < 7; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 8) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (128 * i + 16 * k + 8 * (m / 8) + (k + m) % 8); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[8 * l] = _mm512_load_ps(p0 + xss[l]); - is[8 * l] = _mm512_load_ps(p0 + xss[l] + 16); - - for (unsigned j = 1; j < 8; ++j) { - rs[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[8 * l]); - is[8 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[8 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0 + xss[l], rn); - _mm512_store_ps(p0 + xss[l] + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate4LLLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[15]; - - auto s = UnitarySpace::Create(4); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]) | (1 << qs[3]); - - for (unsigned i = 0; i < 15; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 16) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (256 * i + 16 * k + 16 * (m / 16) + (k + m) % 16); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[16 * l] = _mm512_load_ps(p0); - is[16 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 16; ++j) { - rs[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[16 * l]); - is[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[16 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - void ApplyControlledGate4LLLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 4, emaskl); - - for (auto q : qs) { - if (q > 3) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 15; - - unsigned p[16]; - __m512i idx[15]; - - auto s = UnitarySpace::Create(4); - __m512* w = (__m512*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]) | (1 << qs[2]) | (1 << qs[3]); - - for (unsigned i = 0; i < 15; ++i) { - for (unsigned j = 0; j < 16; ++j) { - p[j] = MaskedAdd(j, i + 1, qmask, 16) | (j & (-1 ^ qmask)); - } - - idx[i] = _mm512_set_epi32(p[15], p[14], p[13], p[12], p[11], p[10], - p[9], p[8], p[7], p[6], p[5], p[4], - p[3], p[2], p[1], p[0]); - } - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 16; ++j) { - unsigned k = bits::CompressBits(j, 4, qmask); - p[j] = 2 * (256 * i + 16 * k + 16 * (m / 16) + (k + m) % 16); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 16; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[16 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 16; ++j) { - wf[16 * l + j + 16] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m512* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - const __m512i* idx, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m512 rn, in; - __m512 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[16 * l] = _mm512_load_ps(p0); - is[16 * l] = _mm512_load_ps(p0 + 16); - - for (unsigned j = 1; j < 16; ++j) { - rs[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], rs[16 * l]); - is[16 * l + j] = _mm512_permutexvar_ps(idx[j - 1], is[16 * l]); - } - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm512_mul_ps(rs[0], w[j]); - in = _mm512_mul_ps(rs[0], w[j + 1]); - rn = _mm512_fnmadd_ps(is[0], w[j + 1], rn); - in = _mm512_fmadd_ps(is[0], w[j], in); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm512_fmadd_ps(rs[n], w[j], rn); - in = _mm512_fmadd_ps(rs[n], w[j + 1], in); - rn = _mm512_fnmadd_ps(is[n], w[j + 1], rn); - in = _mm512_fmadd_ps(is[n], w[j], in); - - j += 2; - } - - _mm512_store_ps(p0, rn); - _mm512_store_ps(p0 + 16, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, idx, size, raw_size, rstate); - } - - static unsigned MaskedAdd( - unsigned a, unsigned b, unsigned mask, unsigned lsize) { - unsigned c = bits::CompressBits(a, 4, mask); - return bits::ExpandBits((c + b) % lsize, 4, mask); } For for_; diff --git a/lib/unitary_calculator_basic.h b/lib/unitary_calculator_basic.h index df5a3067..6b1821a9 100644 --- a/lib/unitary_calculator_basic.h +++ b/lib/unitary_calculator_basic.h @@ -15,12 +15,12 @@ #ifndef UNITARY_CALCULATOR_BASIC_H_ #define UNITARY_CALCULATOR_BASIC_H_ - -#include #include #include +#include +#include -#include "bits.h" +#include "simulator.h" #include "unitaryspace_basic.h" namespace qsim { @@ -30,7 +30,7 @@ namespace unitary { * Quantum circuit unitary calculator without vectorization. */ template -class UnitaryCalculatorBasic final { +class UnitaryCalculatorBasic final : public SimulatorBase { public: using UnitarySpace = UnitarySpaceBasic; using Unitary = typename UnitarySpace::Unitary; @@ -49,27 +49,27 @@ class UnitaryCalculatorBasic final { * @param state The state of the system, to be updated by this method. */ void ApplyGate(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { + const fp_type* matrix, State& state) const { // Assume qs[0] < qs[1] < qs[2] < ... . switch (qs.size()) { case 1: - ApplyGate1H(qs, matrix, state); + ApplyGateH<1>(qs, matrix, state); break; case 2: - ApplyGate2H(qs, matrix, state); + ApplyGateH<2>(qs, matrix, state); break; case 3: - ApplyGate3H(qs, matrix, state); + ApplyGateH<3>(qs, matrix, state); break; case 4: - ApplyGate4H(qs, matrix, state); + ApplyGateH<4>(qs, matrix, state); break; case 5: - ApplyGate5H(qs, matrix, state); + ApplyGateH<5>(qs, matrix, state); break; case 6: - ApplyGate6H(qs, matrix, state); + ApplyGateH<6>(qs, matrix, state); break; default: // Not implemented. @@ -81,13 +81,15 @@ class UnitaryCalculatorBasic final { * Applies a controlled gate using non-vectorized instructions. * @param qs Indices of the qubits affected by this gate. * @param cqs Indices of control qubits. - * @param cmask Bit mask of control qubit values. + * @param cvals Bit mask of control qubit values. * @param matrix Matrix representation of the gate to be applied. * @param state The state of the system, to be updated by this method. */ void ApplyControlledGate(const std::vector& qs, - const std::vector& cqs, uint64_t cmask, - const fp_type* matrix, Unitary& state) const { + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { + // Assume qs[0] < qs[1] < qs[2] < ... . + if (cqs.size() == 0) { ApplyGate(qs, matrix, state); return; @@ -95,16 +97,16 @@ class UnitaryCalculatorBasic final { switch (qs.size()) { case 1: - ApplyControlledGate1H(qs, cqs, cmask, matrix, state); + ApplyControlledGateH<1>(qs, cqs, cvals, matrix, state); break; case 2: - ApplyControlledGate2H(qs, cqs, cmask, matrix, state); + ApplyControlledGateH<2>(qs, cqs, cvals, matrix, state); break; case 3: - ApplyControlledGate3H(qs, cqs, cmask, matrix, state); + ApplyControlledGateH<3>(qs, cqs, cvals, matrix, state); break; case 4: - ApplyControlledGate4H(qs, cqs, cmask, matrix, state); + ApplyControlledGateH<4>(qs, cqs, cvals, matrix, state); break; default: // Not implemented. @@ -120,793 +122,132 @@ class UnitaryCalculatorBasic final { } private: - void ApplyGate1H(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - + template + void ApplyGateH(const std::vector& qs, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - fp_type rn, in; - fp_type rs[2], is[2]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (1 * ii & ms[0]) | (2 * ii & ms[1]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 1; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); - } - - void ApplyGate2H(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } + const uint64_t* ms, const uint64_t* xss, uint64_t size, + uint64_t row_size, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { fp_type rn, in; - fp_type rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (1 * ii & ms[0]) | (2 * ii & ms[1]) | (4 * ii & ms[2]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 2; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); - } + fp_type rs[hsize], is[hsize]; - void ApplyGate3H(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; + uint64_t r = i % size; + uint64_t s = i / size; - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } + uint64_t t = r & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + r *= 2; + t |= r & ms[j]; } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - fp_type rn, in; - fp_type rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (1 * ii & ms[0]) | (2 * ii & ms[1]) | (4 * ii & ms[2]) - | (8 * ii & ms[3]); - auto p0 = rstate + row_size * r + 2 * c; + auto p0 = rstate + row_size * s + 2 * t; - for (unsigned l = 0; l < 8; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = *(p0 + xss[k]); + is[k] = *(p0 + xss[k] + 1); } uint64_t j = 0; - for (unsigned l = 0; l < 8; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = rs[0] * v[j] - is[0] * v[j + 1]; in = rs[0] * v[j + 1] + is[0] * v[j]; j += 2; - for (unsigned n = 1; n < 8; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; + for (unsigned l = 1; l < hsize; ++l) { + rn += rs[l] * v[j] - is[l] * v[j + 1]; + in += rs[l] * v[j + 1] + is[l] * v[j]; j += 2; } - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; + *(p0 + xss[k]) = rn; + *(p0 + xss[k] + 1) = in; } }; - fp_type* rstate = state.get(); - - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); - } - - void ApplyGate4H(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - fp_type rn, in; - fp_type rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (1 * ii & ms[0]) | (2 * ii & ms[1]) | (4 * ii & ms[2]) - | (8 * ii & ms[3]) | (16 * ii & ms[4]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; - fp_type* rstate = state.get(); + FillIndices(state.num_qubits(), qs, ms, xss); - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + unsigned n = state.num_qubits() > H ? state.num_qubits() - H : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); + for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, state.get()); } - void ApplyGate5H(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - + template + void ApplyControlledGateH(const std::vector& qs, + const std::vector& cqs, + uint64_t cvals, const fp_type* matrix, + State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - fp_type rn, in; - fp_type rs[32], is[32]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (1 * ii & ms[0]) | (2 * ii & ms[1]) | (4 * ii & ms[2]) - | (8 * ii & ms[3]) | (16 * ii & ms[4]) | (32 * ii & ms[5]); - - auto p0 = rstate + row_size * r + 2 * c; + const uint64_t* ms, const uint64_t* xss, uint64_t cvalsh, + uint64_t cmaskh, uint64_t size, uint64_t row_size, + fp_type* rstate) { + constexpr unsigned hsize = 1 << H; - for (unsigned l = 0; l < 32; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); - } - - void ApplyGate6H(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[6]; - uint64_t ms[7]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 6; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[6] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[5] - 1); - - uint64_t xss[64]; - for (unsigned i = 0; i < 64; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 6; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { fp_type rn, in; - fp_type rs[64], is[64]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (1 * ii & ms[0]) | (2 * ii & ms[1]) | (4 * ii & ms[2]) - | (8 * ii & ms[3]) | (16 * ii & ms[4]) | (32 * ii & ms[5]) - | (64 * ii & ms[6]); + fp_type rs[hsize], is[hsize]; - auto p0 = rstate + row_size * r + 2 * c; + uint64_t r = i % size; + uint64_t s = i / size; - for (unsigned l = 0; l < 64; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); + uint64_t t = r & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + r *= 2; + t |= r & ms[j]; } - uint64_t j = 0; - - for (unsigned l = 0; l < 64; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; + if ((t & cmaskh) == cvalsh) { + auto p0 = rstate + row_size * s + 2 * t; - j += 2; + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = *(p0 + xss[k]); + is[k] = *(p0 + xss[k] + 1); } - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); - } - - void ApplyControlledGate1H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - fp_type rn, in; - fp_type rs[2], is[2]; + uint64_t j = 0; - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; + for (unsigned k = 0; k < hsize; ++k) { + rn = rs[0] * v[j] - is[0] * v[j + 1]; + in = rs[0] * v[j + 1] + is[0] * v[j]; j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 1 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate2H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - emaskh = ~emaskh; + for (unsigned l = 1; l < hsize; ++l) { + rn += rs[l] * v[j] - is[l] * v[j + 1]; + in += rs[l] * v[j + 1] + is[l] * v[j]; - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - fp_type rn, in; - fp_type rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; + j += 2; + } - for (unsigned l = 0; l < 4; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; + *(p0 + xss[k]) = rn; + *(p0 + xss[k] + 1) = in; } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; - unsigned k = 2 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate3H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - fp_type rn, in; - fp_type rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate4H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - fp_type rn, in; - fp_type rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = *(p0 + xss[l]); - is[l] = *(p0 + xss[l] + 1); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = rs[0] * v[j] - is[0] * v[j + 1]; - in = rs[0] * v[j + 1] + is[0] * v[j]; - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn += rs[n] * v[j] - is[n] * v[j + 1]; - in += rs[n] * v[j + 1] + is[n] * v[j]; - - j += 2; - } - - *(p0 + xss[l]) = rn; - *(p0 + xss[l] + 1) = in; - } - }; + FillIndices(state.num_qubits(), qs, ms, xss); - fp_type* rstate = state.get(); + auto m = GetMasks7(state.num_qubits(), qs, cqs, cvals); - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; + unsigned n = state.num_qubits() > H ? state.num_qubits() - H : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); + for_.Run(size * size2, f, + matrix, ms, xss, m.cvalsh, m.cmaskh, size, raw_size, state.get()); } For for_; diff --git a/lib/unitary_calculator_sse.h b/lib/unitary_calculator_sse.h index 2e458526..9ff9c98e 100644 --- a/lib/unitary_calculator_sse.h +++ b/lib/unitary_calculator_sse.h @@ -17,21 +17,22 @@ #include -#include #include #include +#include +#include -#include "bits.h" +#include "simulator.h" #include "unitaryspace_sse.h" namespace qsim { namespace unitary { /** - * Quantum circuit unitary calculator with SSE vectorization. + * Quantum circuit simulator with SSE vectorization. */ template -class UnitaryCalculatorSSE final { +class UnitaryCalculatorSSE final : public SimulatorBase { public: using UnitarySpace = UnitarySpaceSSE; using Unitary = typename UnitarySpace::Unitary; @@ -50,60 +51,60 @@ class UnitaryCalculatorSSE final { * @param state The state of the system, to be updated by this method. */ void ApplyGate(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { + const fp_type* matrix, State& state) const { // Assume qs[0] < qs[1] < qs[2] < ... . switch (qs.size()) { case 1: if (qs[0] > 1) { - ApplyGate1H(qs, matrix, state); + ApplyGateH<1>(qs, matrix, state); } else { - ApplyGate1L(qs, matrix, state); + ApplyGateL<0, 1>(qs, matrix, state); } break; case 2: if (qs[0] > 1) { - ApplyGate2HH(qs, matrix, state); + ApplyGateH<2>(qs, matrix, state); } else if (qs[1] > 1) { - ApplyGate2HL(qs, matrix, state); + ApplyGateL<1, 1>(qs, matrix, state); } else { - ApplyGate2LL(qs, matrix, state); + ApplyGateL<0, 2>(qs, matrix, state); } break; case 3: if (qs[0] > 1) { - ApplyGate3HHH(qs, matrix, state); + ApplyGateH<3>(qs, matrix, state); } else if (qs[1] > 1) { - ApplyGate3HHL(qs, matrix, state); + ApplyGateL<2, 1>(qs, matrix, state); } else { - ApplyGate3HLL(qs, matrix, state); + ApplyGateL<1, 2>(qs, matrix, state); } break; case 4: if (qs[0] > 1) { - ApplyGate4HHHH(qs, matrix, state); + ApplyGateH<4>(qs, matrix, state); } else if (qs[1] > 1) { - ApplyGate4HHHL(qs, matrix, state); + ApplyGateL<3, 1>(qs, matrix, state); } else { - ApplyGate4HHLL(qs, matrix, state); + ApplyGateL<2, 2>(qs, matrix, state); } break; case 5: if (qs[0] > 1) { - ApplyGate5HHHHH(qs, matrix, state); + ApplyGateH<5>(qs, matrix, state); } else if (qs[1] > 1) { - ApplyGate5HHHHL(qs, matrix, state); + ApplyGateL<4, 1>(qs, matrix, state); } else { - ApplyGate5HHHLL(qs, matrix, state); + ApplyGateL<3, 2>(qs, matrix, state); } break; case 6: if (qs[0] > 1) { - ApplyGate6HHHHHH(qs, matrix, state); + ApplyGateH<6>(qs, matrix, state); } else if (qs[1] > 1) { - ApplyGate6HHHHHL(qs, matrix, state); + ApplyGateL<5, 1>(qs, matrix, state); } else { - ApplyGate6HHHHLL(qs, matrix, state); + ApplyGateL<4, 2>(qs, matrix, state); } break; default: @@ -116,13 +117,16 @@ class UnitaryCalculatorSSE final { * Applies a controlled gate using SSE instructions. * @param qs Indices of the qubits affected by this gate. * @param cqs Indices of control qubits. - * @param cmask Bit mask of control qubit values. + * @param cvals Bit mask of control qubit values. * @param matrix Matrix representation of the gate to be applied. * @param state The state of the system, to be updated by this method. */ void ApplyControlledGate(const std::vector& qs, - const std::vector& cqs, uint64_t cmask, - const fp_type* matrix, Unitary& state) const { + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { + // Assume qs[0] < qs[1] < qs[2] < ... . + // Assume cqs[0] < cqs[1] < cqs[2] < ... . + if (cqs.size() == 0) { ApplyGate(qs, matrix, state); return; @@ -132,78 +136,78 @@ class UnitaryCalculatorSSE final { case 1: if (qs[0] > 1) { if (cqs[0] > 1) { - ApplyControlledGate1H_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate1H_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<1>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 1) { - ApplyControlledGate1L_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate1L_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 1, 0>(qs, cqs, cvals, matrix, state); } } break; case 2: if (qs[0] > 1) { if (cqs[0] > 1) { - ApplyControlledGate2HH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<2>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2HH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<2>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 1) { if (cqs[0] > 1) { - ApplyControlledGate2HL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2HL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 1, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 1) { - ApplyControlledGate2LL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate2LL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<0, 2, 0>(qs, cqs, cvals, matrix, state); } } break; case 3: if (qs[0] > 1) { if (cqs[0] > 1) { - ApplyControlledGate3HHH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<3>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HHH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<3>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 1) { if (cqs[0] > 1) { - ApplyControlledGate3HHL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HHL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 1, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 1) { - ApplyControlledGate3HLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate3HLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<1, 2, 0>(qs, cqs, cvals, matrix, state); } } break; case 4: if (qs[0] > 1) { if (cqs[0] > 1) { - ApplyControlledGate4HHHH_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateHH<4>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHHH_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateHL<4>(qs, cqs, cvals, matrix, state); } } else if (qs[1] > 1) { if (cqs[0] > 1) { - ApplyControlledGate4HHHL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<3, 1, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHHL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<3, 1, 0>(qs, cqs, cvals, matrix, state); } } else { if (cqs[0] > 1) { - ApplyControlledGate4HHLL_H(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 2, 1>(qs, cqs, cvals, matrix, state); } else { - ApplyControlledGate4HHLL_L(qs, cqs, cmask, matrix, state); + ApplyControlledGateL<2, 2, 0>(qs, cqs, cvals, matrix, state); } } break; @@ -221,46 +225,36 @@ class UnitaryCalculatorSSE final { } private: - void ApplyGate1H(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - + template + void ApplyGateH(const std::vector& qs, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { + const uint64_t* ms, const uint64_t* xss, uint64_t size, + uint64_t row_size, fp_type* rstate) { + constexpr unsigned hsize = 1 << H; + __m128 ru, iu, rn, in; - __m128 rs[2], is[2]; + __m128 rs[hsize], is[hsize]; - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]); + uint64_t r = 4 * (i % size); + uint64_t s = i / size; + + uint64_t t = r & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + r *= 2; + t |= r & ms[j]; + } - auto p0 = rstate + row_size * r + 2 * c; + auto p0 = rstate + row_size * s + 2 * t; - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = _mm_load_ps(p0 + xss[k]); + is[k] = _mm_load_ps(p0 + xss[k] + 4); } uint64_t j = 0; - for (unsigned l = 0; l < 2; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm_set1_ps(v[j]); iu = _mm_set1_ps(v[j + 1]); rn = _mm_mul_ps(rs[0], ru); @@ -270,85 +264,84 @@ class UnitaryCalculatorSSE final { j += 2; - for (unsigned n = 1; n < 2; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm_set1_ps(v[j]); iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); + rn = _mm_add_ps(rn, _mm_mul_ps(rs[l], ru)); + in = _mm_add_ps(in, _mm_mul_ps(rs[l], iu)); + rn = _mm_sub_ps(rn, _mm_mul_ps(is[l], iu)); + in = _mm_add_ps(in, _mm_mul_ps(is[l], ru)); j += 2; } - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); + _mm_store_ps(p0 + xss[k], rn); + _mm_store_ps(p0 + xss[k] + 4, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; - unsigned k = 3; + FillIndices(state.num_qubits(), qs, ms, xss); + + unsigned k = 2 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); + for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, state.get()); } - void ApplyGate1L(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - unsigned p[4]; - - auto s = UnitarySpace::Create(2); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } + template + void ApplyGateL(const std::vector& qs, + const fp_type* matrix, State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, + const uint64_t* ms, const uint64_t* xss, unsigned q0, + uint64_t size, uint64_t row_size, fp_type* rstate) { + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; - unsigned l = 2 * (2 * i + m); + __m128 rn, in; + __m128 rs[gsize], is[gsize]; - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } + uint64_t r = 4 * (i % size); + uint64_t s = i / size; - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } + uint64_t t = r & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + r *= 2; + t |= r & ms[j]; } - } - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - unsigned q0, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[2], is[2]; + auto p0 = rstate + row_size * s + 2 * t; - uint64_t ii = i % size; - uint64_t r = i / size; - auto p0 = rstate + row_size * r + 8 * ii; + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm_load_ps(p0); - is[2 * l] = _mm_load_ps(p0 + 4); + rs[k2] = _mm_load_ps(p0 + xss[k]); + is[k2] = _mm_load_ps(p0 + xss[k] + 4); - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); + if (L == 1) { + rs[k2 + 1] = q0 == 0 ? _mm_shuffle_ps(rs[k2], rs[k2], 177) + : _mm_shuffle_ps(rs[k2], rs[k2], 78); + is[k2 + 1] = q0 == 0 ? _mm_shuffle_ps(is[k2], is[k2], 177) + : _mm_shuffle_ps(is[k2], is[k2], 78); + } else if (L == 2) { + rs[k2 + 1] = _mm_shuffle_ps(rs[k2], rs[k2], 57); + is[k2 + 1] = _mm_shuffle_ps(is[k2], is[k2], 57); + rs[k2 + 2] = _mm_shuffle_ps(rs[k2], rs[k2], 78); + is[k2 + 2] = _mm_shuffle_ps(is[k2], is[k2], 78); + rs[k2 + 3] = _mm_shuffle_ps(rs[k2], rs[k2], 147); + is[k2 + 3] = _mm_shuffle_ps(is[k2], is[k2], 147); + } } uint64_t j = 0; - for (unsigned l = 0; l < 1; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm_mul_ps(rs[0], w[j]); in = _mm_mul_ps(rs[0], w[j + 1]); rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); @@ -356,75 +349,72 @@ class UnitaryCalculatorSSE final { j += 2; - for (unsigned n = 1; n < 2; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm_add_ps(rn, _mm_mul_ps(rs[l], w[j])); + in = _mm_add_ps(in, _mm_mul_ps(rs[l], w[j + 1])); + rn = _mm_sub_ps(rn, _mm_mul_ps(is[l], w[j + 1])); + in = _mm_add_ps(in, _mm_mul_ps(is[l], w[j])); j += 2; } - _mm_store_ps(p0, rn); - _mm_store_ps(p0 + 4, in); + _mm_store_ps(p0 + xss[k], rn); + _mm_store_ps(p0 + xss[k] + 4, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + __m128 w[1 << (1 + 2 * H + L)]; + + auto m = GetMasks11(qs); - unsigned k = 2; + FillIndices(state.num_qubits(), qs, ms, xss); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); + + unsigned k = 2 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, w, qs[0], size, raw_size, rstate); + for_.Run(size * size2, f, w, ms, xss, qs[0], size, raw_size, state.get()); } - void ApplyGate2HH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; + template + void ApplyControlledGateHH(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { + auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, + const uint64_t* ms, const uint64_t* xss, uint64_t cvalsh, + uint64_t cmaskh, uint64_t size, uint64_t row_size, + fp_type* rstate) { + constexpr unsigned hsize = 1 << H; - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); + __m128 ru, iu, rn, in; + __m128 rs[hsize], is[hsize]; - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } + uint64_t r = 4 * (i % size); + uint64_t s = i / size; - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[4], is[4]; + uint64_t t = r & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + r *= 2; + t |= r & ms[j]; + } - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]) | (16 * ii & ms[2]); + if ((t & cmaskh) != cvalsh) return; - auto p0 = rstate + row_size * r + 2 * c; + auto p0 = rstate + row_size * s + 2 * t; - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = _mm_load_ps(p0 + xss[k]); + is[k] = _mm_load_ps(p0 + xss[k] + 4); } uint64_t j = 0; - for (unsigned l = 0; l < 4; ++l) { + for (unsigned k = 0; k < hsize; ++k) { ru = _mm_set1_ps(v[j]); iu = _mm_set1_ps(v[j + 1]); rn = _mm_mul_ps(rs[0], ru); @@ -434,191 +424,72 @@ class UnitaryCalculatorSSE final { j += 2; - for (unsigned n = 1; n < 4; ++n) { + for (unsigned l = 1; l < hsize; ++l) { ru = _mm_set1_ps(v[j]); iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); + rn = _mm_add_ps(rn, _mm_mul_ps(rs[l], ru)); + in = _mm_add_ps(in, _mm_mul_ps(rs[l], iu)); + rn = _mm_sub_ps(rn, _mm_mul_ps(is[l], iu)); + in = _mm_add_ps(in, _mm_mul_ps(is[l], ru)); j += 2; } - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); + _mm_store_ps(p0 + xss[k], rn); + _mm_store_ps(p0 + xss[k] + 4, in); } }; - fp_type* rstate = state.get(); + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; - unsigned k = 4; + auto m = GetMasks7(state.num_qubits(), qs, cqs, cvals); + FillIndices(state.num_qubits(), qs, ms, xss); + + unsigned k = 2 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); + for_.Run(size * size2, f, + matrix, ms, xss, m.cvalsh, m.cmaskh, size, raw_size, state.get()); } - void ApplyGate2HL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = UnitarySpace::Create(3); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - + template + void ApplyControlledGateHL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned q0, uint64_t size, uint64_t row_size, + const uint64_t* ms, const uint64_t* xss, uint64_t cvalsh, + uint64_t cmaskh, uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, qs[0], size, raw_size, rstate); - } - - void ApplyGate2LL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - unsigned p[4]; - - auto s = UnitarySpace::Create(2); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); + constexpr unsigned hsize = 1 << H; - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); + __m128 rn, in; + __m128 rs[hsize], is[hsize]; - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } + uint64_t r = 4 * (i % size); + uint64_t s = i / size; - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } + uint64_t t = r & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + r *= 2; + t |= r & ms[j]; } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - uint64_t ii = i % size; - uint64_t r = i / size; - auto p0 = rstate + row_size * r + 8 * ii; + if ((t & cmaskh) != cvalsh) return; - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm_load_ps(p0); - is[4 * l] = _mm_load_ps(p0 + 4); + auto p0 = rstate + row_size * s + 2 * t; - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); + for (unsigned k = 0; k < hsize; ++k) { + rs[k] = _mm_load_ps(p0 + xss[k]); + is[k] = _mm_load_ps(p0 + xss[k] + 4); } uint64_t j = 0; - for (unsigned l = 0; l < 1; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm_mul_ps(rs[0], w[j]); in = _mm_mul_ps(rs[0], w[j + 1]); rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); @@ -626,295 +497,90 @@ class UnitaryCalculatorSSE final { j += 2; - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); + for (unsigned l = 1; l < hsize; ++l) { + rn = _mm_add_ps(rn, _mm_mul_ps(rs[l], w[j])); + in = _mm_add_ps(in, _mm_mul_ps(rs[l], w[j + 1])); + rn = _mm_sub_ps(rn, _mm_mul_ps(is[l], w[j + 1])); + in = _mm_add_ps(in, _mm_mul_ps(is[l], w[j])); j += 2; } - _mm_store_ps(p0, rn); - _mm_store_ps(p0 + 4, in); + _mm_store_ps(p0 + xss[k], rn); + _mm_store_ps(p0 + xss[k] + 4, in); } }; - fp_type* rstate = state.get(); - - unsigned k = 2; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, size, raw_size, rstate); - } - - void ApplyGate3HHH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]) | (16 * ii & ms[2]) - | (32 * ii & ms[3]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + __m128 w[1 << (1 + 2 * H)]; - fp_type* rstate = state.get(); + auto m = GetMasks8<2>(state.num_qubits(), qs, cqs, cvals); + FillIndices(state.num_qubits(), qs, ms, xss); + FillControlledMatrixH(m.cvalsl, m.cmaskl, matrix, (fp_type*) w); - unsigned k = 5; + unsigned k = 2 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); + for_.Run(size * size2, f, + w, ms, xss, m.cvalsh, m.cmaskh, size, raw_size, state.get()); } - void ApplyGate3HHL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = UnitarySpace::Create(4); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - + template + void ApplyControlledGateL(const std::vector& qs, + const std::vector& cqs, uint64_t cvals, + const fp_type* matrix, State& state) const { auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned q0, uint64_t size, uint64_t row_size, + const uint64_t* ms, const uint64_t* xss, uint64_t cvalsh, + uint64_t cmaskh, unsigned q0, uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]) | (16 * ii & ms[2]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, qs[0], size, raw_size, rstate); - } + constexpr unsigned gsize = 1 << (H + L); + constexpr unsigned hsize = 1 << H; + constexpr unsigned lsize = 1 << L; - void ApplyGate3HLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; + __m128 rn, in; + __m128 rs[gsize], is[gsize]; - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); + uint64_t r = 4 * (i % size); + uint64_t s = i / size; - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } + uint64_t t = r & ms[0]; + for (unsigned j = 1; j <= H; ++j) { + r *= 2; + t |= r & ms[j]; } - xss[i] = a; - } - unsigned p[4]; + if ((t & cmaskh) != cvalsh) return; - auto s = UnitarySpace::Create(3); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; + auto p0 = rstate + row_size * s + 2 * t; - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); + for (unsigned k = 0; k < hsize; ++k) { + unsigned k2 = lsize * k; - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } + rs[k2] = _mm_load_ps(p0 + xss[k]); + is[k2] = _mm_load_ps(p0 + xss[k] + 4); - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; + if (L == 1) { + rs[k2 + 1] = q0 == 0 ? _mm_shuffle_ps(rs[k2], rs[k2], 177) + : _mm_shuffle_ps(rs[k2], rs[k2], 78); + is[k2 + 1] = q0 == 0 ? _mm_shuffle_ps(is[k2], is[k2], 177) + : _mm_shuffle_ps(is[k2], is[k2], 78); + } else if (L == 2) { + rs[k2 + 1] = _mm_shuffle_ps(rs[k2], rs[k2], 57); + is[k2 + 1] = _mm_shuffle_ps(is[k2], is[k2], 57); + rs[k2 + 2] = _mm_shuffle_ps(rs[k2], rs[k2], 78); + is[k2 + 2] = _mm_shuffle_ps(is[k2], is[k2], 78); + rs[k2 + 3] = _mm_shuffle_ps(rs[k2], rs[k2], 147); + is[k2 + 3] = _mm_shuffle_ps(is[k2], is[k2], 147); } } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } uint64_t j = 0; - for (unsigned l = 0; l < 2; ++l) { + for (unsigned k = 0; k < hsize; ++k) { rn = _mm_mul_ps(rs[0], w[j]); in = _mm_mul_ps(rs[0], w[j + 1]); rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); @@ -922,3612 +588,46 @@ class UnitaryCalculatorSSE final { j += 2; - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); + for (unsigned l = 1; l < gsize; ++l) { + rn = _mm_add_ps(rn, _mm_mul_ps(rs[l], w[j])); + in = _mm_add_ps(in, _mm_mul_ps(rs[l], w[j + 1])); + rn = _mm_sub_ps(rn, _mm_mul_ps(is[l], w[j + 1])); + in = _mm_add_ps(in, _mm_mul_ps(is[l], w[j])); j += 2; } - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); + _mm_store_ps(p0 + xss[k], rn); + _mm_store_ps(p0 + xss[k] + 4, in); } }; - fp_type* rstate = state.get(); - - unsigned k = 3; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, size, raw_size, rstate); - } - - void ApplyGate4HHHH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]) | (16 * ii & ms[2]) - | (32 * ii & ms[3]) | (64 * ii & ms[4]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; + uint64_t ms[H + 1]; + uint64_t xss[1 << H]; + __m128 w[1 << (1 + 2 * H + L)]; - fp_type* rstate = state.get(); + FillIndices(state.num_qubits(), qs, ms, xss); - unsigned k = 6; + unsigned k = 2 + H; unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; uint64_t size = uint64_t{1} << n; uint64_t size2 = uint64_t{1} << state.num_qubits(); uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); - } + if (CH) { + auto m = GetMasks9(state.num_qubits(), qs, cqs, cvals); + FillMatrix(m.qmaskl, matrix, (fp_type*) w); - void ApplyGate4HHHL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; + for_.Run(size * size2, f, w, ms, xss, + m.cvalsh, m.cmaskh, qs[0], size, raw_size, state.get()); + } else { + auto m = GetMasks10(state.num_qubits(), qs, cqs, cvals); + FillControlledMatrixL( + m.cvalsl, m.cmaskl, m.qmaskl, matrix, (fp_type*) w); - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); + for_.Run(size * size2, f, w, ms, xss, + m.cvalsh, m.cmaskh, qs[0], size, raw_size, state.get()); } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = UnitarySpace::Create(5); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned q0, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]) | (16 * ii & ms[2]) - | (32 * ii & ms[3]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, qs[0], size, raw_size, rstate); - } - - void ApplyGate4HHLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = UnitarySpace::Create(4); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]) | (16 * ii & ms[2]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, size, raw_size, rstate); - } - - void ApplyGate5HHHHH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[32], is[32]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]) | (16 * ii & ms[2]) - | (32 * ii & ms[3]) | (64 * ii & ms[4]) | (128 * ii & ms[5]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 32; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); - } - - void ApplyGate5HHHHL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = UnitarySpace::Create(6); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (64 * i + 32 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned q0, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[32], is[32]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]) | (16 * ii & ms[2]) - | (32 * ii & ms[3]) | (64 * ii & ms[4]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, qs[0], size, raw_size, rstate); - } - - void ApplyGate5HHHLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = UnitarySpace::Create(5); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 32; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (128 * i + 32 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (32 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[32], is[32]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]) | (16 * ii & ms[2]) - | (32 * ii & ms[3]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 32; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, size, raw_size, rstate); - } - - void ApplyGate6HHHHHH(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[6]; - uint64_t ms[7]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 6; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[6] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[5] - 1); - - uint64_t xss[64]; - for (unsigned i = 0; i < 64; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 6; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[64], is[64]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]) | (16 * ii & ms[2]) - | (32 * ii & ms[3]) | (64 * ii & ms[4]) | (128 * ii & ms[5]) - | (256 * ii & ms[6]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 64; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 64; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 8; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, size, raw_size, rstate); - } - - void ApplyGate6HHHHHL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[5]; - uint64_t ms[6]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 5; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[5] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[4] - 1); - - uint64_t xss[32]; - for (unsigned i = 0; i < 32; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 5; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = UnitarySpace::Create(7); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 32; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (128 * i + 64 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned q0, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[64], is[64]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]) | (16 * ii & ms[2]) - | (32 * ii & ms[3]) | (64 * ii & ms[4]) | (128 * ii & ms[5]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 32; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 32; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 7; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, qs[0], size, raw_size, rstate); - } - - void ApplyGate6HHHHLL(const std::vector& qs, - const fp_type* matrix, Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned p[4]; - - auto s = UnitarySpace::Create(6); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 64; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (256 * i + 64 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (64 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[64], is[64]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = (4 * ii & ms[0]) | (8 * ii & ms[1]) | (16 * ii & ms[2]) - | (32 * ii & ms[3]) | (64 * ii & ms[4]); - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 64; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, size, raw_size, rstate); - } - - void ApplyControlledGate1H_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[2], is[2]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate1H_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(2); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (2 * i + 2 * k + m); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 2 == (p[j] / 2) % 2 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[2], is[2]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate1L_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(2); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[2], is[2]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm_load_ps(p0); - is[2 * l] = _mm_load_ps(p0 + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0, rn); - _mm_store_ps(p0 + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 2 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, qs[0], size, raw_size, rstate); - } - - void ApplyControlledGate1L_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(2); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 2; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (4 * i + 2 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (2 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 2 == (p[j] / 2) % 2 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[2], is[2]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[2 * l] = _mm_load_ps(p0); - is[2 * l] = _mm_load_ps(p0 + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 2; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0, rn); - _mm_store_ps(p0 + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 2 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, qs[0], size, raw_size, rstate); - } - - void ApplyControlledGate2HH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate2HH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(3); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (4 * i + 4 * k + m); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate2HL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(3); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, qs[0], size, raw_size, rstate); - } - - void ApplyControlledGate2HL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(3); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (8 * i + 4 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, qs[0], size, raw_size, rstate); - } - - void ApplyControlledGate2LL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(2); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm_load_ps(p0); - is[4 * l] = _mm_load_ps(p0 + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0, rn); - _mm_store_ps(p0 + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 2 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate2LL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(2); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 1; ++i) { - for (unsigned m = 0; m < 4; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 4 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (4 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 4 == (p[j] / 2) % 4 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[4], is[4]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 1; ++l) { - rs[4 * l] = _mm_load_ps(p0); - is[4 * l] = _mm_load_ps(p0 + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 1; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 4; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0, rn); - _mm_store_ps(p0 + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 2 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate3HHH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate3HHH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(4); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (8 * i + 8 * k + m); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate3HHL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(4); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, qs[0], size, raw_size, rstate); - } - - void ApplyControlledGate3HHL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(4); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 8 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, qs[0], size, raw_size, rstate); - } - - void ApplyControlledGate3HLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(3); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate3HLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[1]; - uint64_t ms[2]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - ms[1] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[0] - 1); - - uint64_t xss[2]; - for (unsigned i = 0; i < 2; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 1; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(3); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 2; ++i) { - for (unsigned m = 0; m < 8; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (32 * i + 8 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (8 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 8 == (p[j] / 2) % 8 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[8], is[8]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 2; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 2; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 8; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 3 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate4HHHH_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - auto f = [](unsigned n, unsigned m, uint64_t i, const fp_type* v, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 ru, iu, rn, in; - __m128 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_mul_ps(rs[0], ru); - in = _mm_mul_ps(rs[0], iu); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[0], ru)); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - ru = _mm_set1_ps(v[j]); - iu = _mm_set1_ps(v[j + 1]); - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], ru)); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], iu)); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], iu)); - in = _mm_add_ps(in, _mm_mul_ps(is[n], ru)); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, matrix, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate4HHHH_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[4]; - uint64_t ms[5]; - - xs[0] = uint64_t{1} << (qs[0] + 1); - ms[0] = (uint64_t{1} << qs[0]) - 1; - for (unsigned i = 1; i < 4; ++i) { - xs[i] = uint64_t{1} << (qs[i + 0] + 1); - ms[i] = ((uint64_t{1} << qs[i + 0]) - 1) ^ (xs[i - 1] - 1); - } - ms[4] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[3] - 1); - - uint64_t xss[16]; - for (unsigned i = 0; i < 16; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 4; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - emaskh |= uint64_t{1} << q; - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(5); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 16; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (16 * i + 16 * k + m); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 16; ++l) { - rs[l] = _mm_load_ps(p0 + xss[l]); - is[l] = _mm_load_ps(p0 + xss[l] + 4); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 16; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 6 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate4HHHL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(5); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, qs[0], size, raw_size, rstate); - } - - void ApplyControlledGate4HHHL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[3]; - uint64_t ms[4]; - - xs[0] = uint64_t{1} << (qs[1] + 1); - ms[0] = (uint64_t{1} << qs[1]) - 1; - for (unsigned i = 1; i < 3; ++i) { - xs[i] = uint64_t{1} << (qs[i + 1] + 1); - ms[i] = ((uint64_t{1} << qs[i + 1]) - 1) ^ (xs[i - 1] - 1); - } - ms[3] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[2] - 1); - - uint64_t xss[8]; - for (unsigned i = 0; i < 8; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 3; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(5); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]); - - for (unsigned i = 0; i < 8; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (32 * i + 16 * k + 2 * (m / 2) + (k + m) % 2); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - unsigned q0, uint64_t size, uint64_t row_size, - fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 8; ++l) { - rs[2 * l] = _mm_load_ps(p0 + xss[l]); - is[2 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(rs[2 * l], rs[2 * l], 177) - : _mm_shuffle_ps(rs[2 * l], rs[2 * l], 78); - is[2 * l + 1] = q0 == 0 ? _mm_shuffle_ps(is[2 * l], is[2 * l], 177) - : _mm_shuffle_ps(is[2 * l], is[2 * l], 78); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 8; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 5 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, qs[0], size, raw_size, rstate); - } - - void ApplyControlledGate4HHLL_H(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - uint64_t emaskh = 0; - - for (auto q : cqs) { - emaskh |= uint64_t{1} << q; - } - - uint64_t cmaskh = bits::ExpandBits(cmask, state.num_qubits(), emaskh); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(4); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j] = matrix[p[j]]; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = matrix[p[j] + 1]; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size(); - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - void ApplyControlledGate4HHLL_L(const std::vector& qs, - const std::vector& cqs, - uint64_t cmask, const fp_type* matrix, - Unitary& state) const { - uint64_t xs[2]; - uint64_t ms[3]; - - xs[0] = uint64_t{1} << (qs[2] + 1); - ms[0] = (uint64_t{1} << qs[2]) - 1; - for (unsigned i = 1; i < 2; ++i) { - xs[i] = uint64_t{1} << (qs[i + 2] + 1); - ms[i] = ((uint64_t{1} << qs[i + 2]) - 1) ^ (xs[i - 1] - 1); - } - ms[2] = ((uint64_t{1} << state.num_qubits()) - 1) ^ (xs[1] - 1); - - uint64_t xss[4]; - for (unsigned i = 0; i < 4; ++i) { - uint64_t a = 0; - for (uint64_t k = 0; k < 2; ++k) { - if (((i >> k) & 1) == 1) { - a += xs[k]; - } - } - xss[i] = a; - } - - unsigned cl = 0; - uint64_t emaskl = 0; - uint64_t emaskh = 0; - - for (auto q : cqs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } else { - ++cl; - emaskl |= uint64_t{1} << q; - } - } - - uint64_t cmaskh = bits::ExpandBits(cmask >> cl, state.num_qubits(), emaskh); - uint64_t cmaskl = bits::ExpandBits(cmask & ((1 << cl) - 1), 2, emaskl); - - for (auto q : qs) { - if (q > 1) { - emaskh |= uint64_t{1} << q; - } - } - - emaskh = ~emaskh ^ 3; - - unsigned p[4]; - - auto s = UnitarySpace::Create(4); - __m128* w = (__m128*) s.get(); - fp_type* wf = (fp_type*) w; - - unsigned qmask = (1 << qs[0]) | (1 << qs[1]); - - for (unsigned i = 0; i < 4; ++i) { - for (unsigned m = 0; m < 16; ++m) { - for (unsigned j = 0; j < 4; ++j) { - unsigned k = bits::CompressBits(j, 2, qmask); - p[j] = 2 * (64 * i + 16 * k + 4 * (m / 4) + (k + m) % 4); - } - - unsigned l = 2 * (16 * i + m); - - for (unsigned j = 0; j < 4; ++j) { - fp_type v = (p[j] / 2) / 16 == (p[j] / 2) % 16 ? 1 : 0; - wf[4 * l + j] = cmaskl == (j & emaskl) ? matrix[p[j]] : v; - } - - for (unsigned j = 0; j < 4; ++j) { - wf[4 * l + j + 4] = cmaskl == (j & emaskl) ? matrix[p[j] + 1] : 0; - } - } - } - - auto f = [](unsigned n, unsigned m, uint64_t i, const __m128* w, - const uint64_t* ms, const uint64_t* xss, - unsigned num_qubits, uint64_t cmaskh, uint64_t emaskh, - uint64_t size, uint64_t row_size, fp_type* rstate) { - __m128 rn, in; - __m128 rs[16], is[16]; - - uint64_t ii = i % size; - uint64_t r = i / size; - uint64_t c = bits::ExpandBits(ii, num_qubits, emaskh) | cmaskh; - - auto p0 = rstate + row_size * r + 2 * c; - - for (unsigned l = 0; l < 4; ++l) { - rs[4 * l] = _mm_load_ps(p0 + xss[l]); - is[4 * l] = _mm_load_ps(p0 + xss[l] + 4); - - rs[4 * l + 1] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 57); - is[4 * l + 1] = _mm_shuffle_ps(is[4 * l], is[4 * l], 57); - rs[4 * l + 2] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 78); - is[4 * l + 2] = _mm_shuffle_ps(is[4 * l], is[4 * l], 78); - rs[4 * l + 3] = _mm_shuffle_ps(rs[4 * l], rs[4 * l], 147); - is[4 * l + 3] = _mm_shuffle_ps(is[4 * l], is[4 * l], 147); - } - - uint64_t j = 0; - - for (unsigned l = 0; l < 4; ++l) { - rn = _mm_mul_ps(rs[0], w[j]); - in = _mm_mul_ps(rs[0], w[j + 1]); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[0], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[0], w[j])); - - j += 2; - - for (unsigned n = 1; n < 16; ++n) { - rn = _mm_add_ps(rn, _mm_mul_ps(rs[n], w[j])); - in = _mm_add_ps(in, _mm_mul_ps(rs[n], w[j + 1])); - rn = _mm_sub_ps(rn, _mm_mul_ps(is[n], w[j + 1])); - in = _mm_add_ps(in, _mm_mul_ps(is[n], w[j])); - - j += 2; - } - - _mm_store_ps(p0 + xss[l], rn); - _mm_store_ps(p0 + xss[l] + 4, in); - } - }; - - fp_type* rstate = state.get(); - - unsigned k = 4 + cqs.size() - cl; - unsigned n = state.num_qubits() > k ? state.num_qubits() - k : 0; - uint64_t size = uint64_t{1} << n; - uint64_t size2 = uint64_t{1} << state.num_qubits(); - uint64_t raw_size = UnitarySpace::MinRowSize(state.num_qubits()); - - for_.Run(size * size2, f, w, ms, xss, - state.num_qubits(), cmaskh, emaskh, size, raw_size, rstate); - } - - static unsigned MaskedAdd( - unsigned a, unsigned b, unsigned mask, unsigned lsize) { - unsigned c = bits::CompressBits(a, 2, mask); - return bits::ExpandBits((c + b) % lsize, 2, mask); } For for_; diff --git a/tests/make.sh b/tests/make.sh index 7fd828c8..fbac04f0 100755 --- a/tests/make.sh +++ b/tests/make.sh @@ -44,8 +44,9 @@ g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o statespace_av g++ -O3 -I$path_to_include -L$path_to_lib -mavx512f -mbmi2 -fopenmp -o statespace_avx512_test.x statespace_avx512_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -fopenmp -o statespace_basic_test.x statespace_basic_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -msse4 -fopenmp -o statespace_sse_test.x statespace_sse_test.cc -lgtest -lpthread -g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o unitary_calculator_avx_test.x unitary_calculator_avx_test.cc -lgtest -lpthread -g++ -O3 -I$path_to_include -L$path_to_lib -mavx512f -mfma -fopenmp -o unitary_calculator_avx512_test.x unitary_calculator_avx512_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o unitary_calculator_avx_test.x unitary_calculator_avx_nobmi2_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -mbmi2 -fopenmp -o unitary_calculator_avx_test.x unitary_calculator_avx_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -mavx512f -mbmi2 -mfma -fopenmp -o unitary_calculator_avx512_test.x unitary_calculator_avx512_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -fopenmp -o unitary_calculator_basic_test.x unitary_calculator_basic_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -msse4 -fopenmp -o unitary_calculator_sse_test.x unitary_calculator_sse_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o unitaryspace_avx_test.x unitaryspace_avx_test.cc -lgtest -lpthread From c22bee042e78a1e71c173d1dd4c014fca1c52531 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 2 Dec 2021 08:43:11 -0800 Subject: [PATCH 174/246] Review comments. --- qsimcirq/qsim_simulator.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 904b06d1..d2a00516 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -379,9 +379,10 @@ def _sample_measure_results( for key, op in meas_ops.items(): meas_indices = [qubit_map[qubit] for qubit in op.qubits] invert_mask = op.gate.full_invert_mask() - # Match result order to order in ops, then apply invert mask - permuted_results = full_results[:, meas_indices] - results[key] = np.logical_xor(permuted_results, invert_mask) + # Apply invert mask to re-ordered results + results[key] = np.logical_xor( + full_results[:, meas_indices], invert_mask + ) else: options["c"] = self._translate_circuit( @@ -399,11 +400,9 @@ def _sample_measure_results( options["s"] = self.get_seed() measurements[i] = sampler_fn(options) - for key, bound in bounds.items(): + for key, (start, end) in bounds.items(): invert_mask = meas_ops[key].gate.full_invert_mask() - permutation = list(range(bound[0], bound[1])) - permuted_measurements = measurements[:, permutation] - results[key] = np.logical_xor(permuted_measurements, invert_mask) + results[key] = np.logical_xor(measurements[:, start:end], invert_mask) return results From be42204173ff841a6446dba7b9228930ac378aed Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Thu, 2 Dec 2021 18:47:32 +0100 Subject: [PATCH 175/246] Fix typo. --- tests/make.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/make.sh b/tests/make.sh index fbac04f0..9ccb228d 100755 --- a/tests/make.sh +++ b/tests/make.sh @@ -18,7 +18,6 @@ # Prefer using the Makefile (e.g. `make -C tests/`) if possible. path_to_include=googletest/googletest/include -path_to_lib=googletest/lib path_to_lib=googletest/googletest/make/lib g++ -O3 -I$path_to_include -L$path_to_lib -o bitstring_test.x bitstring_test.cc -lgtest -lpthread @@ -44,7 +43,7 @@ g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o statespace_av g++ -O3 -I$path_to_include -L$path_to_lib -mavx512f -mbmi2 -fopenmp -o statespace_avx512_test.x statespace_avx512_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -fopenmp -o statespace_basic_test.x statespace_basic_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -msse4 -fopenmp -o statespace_sse_test.x statespace_sse_test.cc -lgtest -lpthread -g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o unitary_calculator_avx_test.x unitary_calculator_avx_nobmi2_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o unitary_calculator_avx_nobmi2_test.x unitary_calculator_avx_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -mbmi2 -fopenmp -o unitary_calculator_avx_test.x unitary_calculator_avx_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -mavx512f -mbmi2 -mfma -fopenmp -o unitary_calculator_avx512_test.x unitary_calculator_avx512_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -fopenmp -o unitary_calculator_basic_test.x unitary_calculator_basic_test.cc -lgtest -lpthread From 94957c62b3937e77c81657ed3e2037bacddf4331 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Thu, 2 Dec 2021 20:10:44 +0100 Subject: [PATCH 176/246] Fix typos. --- lib/unitary_calculator_avx.h | 2 +- lib/unitary_calculator_avx512.h | 2 +- lib/unitary_calculator_sse.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/unitary_calculator_avx.h b/lib/unitary_calculator_avx.h index 2bcb6780..5e566ca9 100644 --- a/lib/unitary_calculator_avx.h +++ b/lib/unitary_calculator_avx.h @@ -29,7 +29,7 @@ namespace qsim { namespace unitary { /** - * Quantum circuit simulator with AVX vectorization. + * Quantum circuit unitary calculator with AVX vectorization. */ template class UnitaryCalculatorAVX final : public SimulatorBase { diff --git a/lib/unitary_calculator_avx512.h b/lib/unitary_calculator_avx512.h index 4020e313..81053678 100644 --- a/lib/unitary_calculator_avx512.h +++ b/lib/unitary_calculator_avx512.h @@ -29,7 +29,7 @@ namespace qsim { namespace unitary { /** - * Quantum circuit simulator with AVX512 vectorization. + * Quantum circuit unitary calculator with AVX512 vectorization. */ template class UnitaryCalculatorAVX512 final : public SimulatorBase { diff --git a/lib/unitary_calculator_sse.h b/lib/unitary_calculator_sse.h index 9ff9c98e..a3c3f2eb 100644 --- a/lib/unitary_calculator_sse.h +++ b/lib/unitary_calculator_sse.h @@ -29,7 +29,7 @@ namespace qsim { namespace unitary { /** - * Quantum circuit simulator with SSE vectorization. + * Quantum circuit unitary calculator with SSE vectorization. */ template class UnitaryCalculatorSSE final : public SimulatorBase { From f8df3baaf57b8c72acf9fc3390e780735c3805e8 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 2 Dec 2021 13:13:54 -0800 Subject: [PATCH 177/246] Fix dtype and XOR --- qsimcirq/qsim_simulator.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index d2a00516..b28078c9 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -380,9 +380,7 @@ def _sample_measure_results( meas_indices = [qubit_map[qubit] for qubit in op.qubits] invert_mask = op.gate.full_invert_mask() # Apply invert mask to re-ordered results - results[key] = np.logical_xor( - full_results[:, meas_indices], invert_mask - ) + results[key] = full_results[:, meas_indices] ^ invert_mask else: options["c"] = self._translate_circuit( @@ -394,7 +392,8 @@ def _sample_measure_results( shape=( repetitions, sum(cirq.num_qubits(op) for op in meas_ops.values()), - ) + ), + dtype=int, ) for i in range(repetitions): options["s"] = self.get_seed() @@ -402,7 +401,7 @@ def _sample_measure_results( for key, (start, end) in bounds.items(): invert_mask = meas_ops[key].gate.full_invert_mask() - results[key] = np.logical_xor(measurements[:, start:end], invert_mask) + results[key] = measurements[:, start:end] ^ invert_mask return results From 254304c503772ee1ff79f3082be06183f56c6ed0 Mon Sep 17 00:00:00 2001 From: Michael Broughton Date: Mon, 6 Dec 2021 14:52:26 -0800 Subject: [PATCH 178/246] Adds 1-RDM feature to MPS. --- lib/mps_statespace.h | 148 +++++++++++++++- tests/mps_statespace_test.cc | 315 +++++++++++++++++++++++++++++++++++ 2 files changed, 461 insertions(+), 2 deletions(-) diff --git a/lib/mps_statespace.h b/lib/mps_statespace.h index 9e59599c..1ca77906 100644 --- a/lib/mps_statespace.h +++ b/lib/mps_statespace.h @@ -28,6 +28,7 @@ #include #include "../eigen/Eigen/Dense" +#include "../eigen/unsupported/Eigen/CXX11/Tensor" namespace qsim { @@ -180,8 +181,8 @@ class MPSStateSpace { fp_type* state2_raw = state2.get(); // Contract leftmost blocks together, store result in state1 scratch. - ConstMatrixMap top((Complex*) state2_raw, 2, bond_dim); - ConstMatrixMap bot((Complex*) state1_raw, 2, bond_dim); + ConstMatrixMap top((Complex*)state2_raw, 2, bond_dim); + ConstMatrixMap bot((Complex*)state1_raw, 2, bond_dim); MatrixMap partial_contract((Complex*)(state1_raw + end), bond_dim, bond_dim); MatrixMap partial_contract2( @@ -232,6 +233,149 @@ class MPSStateSpace { return partial_contract(0, 0); } + // Compute the 2x2 1-RDM of state on index. Result written to rdm. + // Requires: scratch to be allocated. + static void ReduceDensityMatrix(MPS& state, MPS& scratch, int index, + fp_type* rdm) { + const auto num_qubits = state.num_qubits(); + const auto bond_dim = state.bond_dim(); + const auto end = Size(state); + const bool last_index = (index == num_qubits - 1); + const auto right_dim = (last_index ? 1 : bond_dim); + auto offset = 0; + fp_type* state_raw = state.get(); + fp_type* scratch_raw = scratch.get(); + + Copy(state, scratch); + + // Contract leftmost blocks together, store result in state scratch. + ConstMatrixMap top((Complex*)scratch_raw, 2, bond_dim); + ConstMatrixMap bot((Complex*)state_raw, 2, bond_dim); + MatrixMap partial_contract((Complex*)(state_raw + end), bond_dim, bond_dim); + MatrixMap partial_contract2( + (Complex*)(state_raw + end + 2 * bond_dim * bond_dim), bond_dim, + 2 * bond_dim); + + partial_contract.setZero(); + partial_contract(0, 0) = 1; + if (index > 0) { + partial_contract.noalias() = top.adjoint() * bot; + } + + // Contract all internal blocks together. + for (unsigned i = 1; i < index; ++i) { + offset = GetBlockOffset(state, i); + + // reshape: + new (&partial_contract2) + MatrixMap((Complex*)(state_raw + end + 2 * bond_dim * bond_dim), + bond_dim, 2 * bond_dim); + + // Merge bot into left boundary merged tensor. + new (&bot) ConstMatrixMap((Complex*)(state_raw + offset), bond_dim, + 2 * bond_dim); + partial_contract2.noalias() = partial_contract * bot; + + // reshape: + new (&partial_contract2) + MatrixMap((Complex*)(state_raw + end + 2 * bond_dim * bond_dim), + 2 * bond_dim, bond_dim); + + // Merge top into partial_contract2. + new (&top) ConstMatrixMap((Complex*)(scratch_raw + offset), 2 * bond_dim, + bond_dim); + partial_contract.noalias() = top.adjoint() * partial_contract2; + } + + // The [bond_dim, bond_dim] block in state_raw now contains the contraction + // up to, but not including index. + // Contract rightmost blocks. + offset = GetBlockOffset(state, num_qubits - 1); + new (&top) ConstMatrixMap((Complex*)(scratch_raw + offset), bond_dim, 2); + new (&bot) ConstMatrixMap((Complex*)(state_raw + offset), bond_dim, 2); + new (&partial_contract) + MatrixMap((Complex*)(scratch_raw + end), bond_dim, bond_dim); + new (&partial_contract2) + MatrixMap((Complex*)(scratch_raw + end + 2 * bond_dim * bond_dim), + bond_dim, 2 * bond_dim); + + partial_contract.setZero(); + partial_contract(0, 0) = 1; + if (index < num_qubits - 1) { + partial_contract.noalias() = top * bot.adjoint(); + } + + for (unsigned i = num_qubits - 2; i > index; i--) { + offset = GetBlockOffset(state, i); + + // reshape: + new (&partial_contract2) + MatrixMap((Complex*)(scratch_raw + end + 2 * bond_dim * bond_dim), + 2 * bond_dim, bond_dim); + + // Merge bot into left boundary merged tensor. + new (&bot) ConstMatrixMap((Complex*)(state_raw + offset), 2 * bond_dim, + bond_dim); + partial_contract2.noalias() = bot * partial_contract.adjoint(); + + // reshape: + new (&partial_contract2) + MatrixMap((Complex*)(scratch_raw + end + 2 * bond_dim * bond_dim), + bond_dim, 2 * bond_dim); + + // Merge top into partial_contract2. + new (&top) ConstMatrixMap((Complex*)(scratch_raw + offset), bond_dim, + 2 * bond_dim); + // [bd, bd] = [2bd, bd] @ [bd, 2bd] + partial_contract.noalias() = top * partial_contract2.adjoint(); + } + + // The [bond_dim, bond_dim] block in scratch_raw now contains the + // contraction down from the end, but not including the index. Begin final + // contraction steps. + + // Get leftmost [bd, bd] contraction and contract with top. + + offset = GetBlockOffset(state, index); + new (&partial_contract) + MatrixMap((Complex*)(state_raw + end), bond_dim, bond_dim); + new (&top) + ConstMatrixMap((Complex*)(state_raw + offset), bond_dim, 2 * right_dim); + new (&partial_contract2) + MatrixMap((Complex*)(scratch_raw + end + 2 * bond_dim * bond_dim), + bond_dim, 2 * right_dim); + partial_contract2.noalias() = partial_contract * top.conjugate(); + // copy the bottom contraction scratch_raw to state_raw to save space. + memcpy(state_raw + end, scratch_raw + end, + bond_dim * bond_dim * 2 * sizeof(fp_type)); + + // Contract top again for correct shape. + fp_type* contract3_target = (last_index ? rdm : scratch_raw); + MatrixMap partial_contract3((Complex*)contract3_target, 2 * right_dim, + 2 * right_dim); + partial_contract3.noalias() = top.transpose() * partial_contract2; + + // If we are contracting the last index, all the needed transforms are done. + if (last_index) { + return; + } + + // Conduct final tensor contraction operations. Cannot be easily compiled to + // matmul. + const Eigen::TensorMap> + t_4d((Complex*)scratch_raw, 2, bond_dim, 2, bond_dim); + const Eigen::TensorMap> + t_2d((Complex*)(state_raw + end), bond_dim, bond_dim); + + const Eigen::array, 2> product_dims = { + Eigen::IndexPair(1, 0), + Eigen::IndexPair(3, 1), + }; + Eigen::TensorMap> out( + (Complex*)rdm, 2, 2); + out = t_4d.contract(t_2d, product_dims); + } + // Testing only. Convert the MPS to a wavefunction under "normal" ordering. // Requires: wf be allocated beforehand with bond_dim * 2 ^ num_qubits -1 // memory. diff --git a/tests/mps_statespace_test.cc b/tests/mps_statespace_test.cc index 4860c1ff..4f86154d 100644 --- a/tests/mps_statespace_test.cc +++ b/tests/mps_statespace_test.cc @@ -586,6 +586,321 @@ TEST(MPSStateSpaceTest, InnerProduct4) { EXPECT_NEAR(f, 0.5524, 1e-4); } +TEST(MPSStateSpaceTest, ReduceDensityMatrixLarge){ + auto ss = MPSStateSpace(1); + auto mps = ss.Create(5, 8); + auto scratch = ss.Create(5, 8); + + // Set to highly entangled five qubit state. + memset(mps.get(), 0, ss.RawSize(mps)); + mps.get()[ 0 ] = 0.2309518310679887 ; + mps.get()[ 1 ] = 0.6567032847786496 ; + mps.get()[ 2 ] = 0.2768328727585293 ; + mps.get()[ 3 ] = 0.6623938314484864 ; + mps.get()[ 16 ] = -0.2726304616546085 ; + mps.get()[ 17 ] = -0.6641345176352952 ; + mps.get()[ 18 ] = 0.3010495535897523 ; + mps.get()[ 19 ] = 0.627667980511763 ; + mps.get()[ 32 ] = -0.3191944318061405 ; + mps.get()[ 33 ] = 0.3643805550045959 ; + mps.get()[ 34 ] = -0.04874347818313596 ; + mps.get()[ 35 ] = 0.6919258000829638 ; + mps.get()[ 36 ] = 0.49609358835838246 ; + mps.get()[ 37 ] = -0.04574935292831311 ; + mps.get()[ 38 ] = 0.09997934427265631 ; + mps.get()[ 39 ] = -0.16126613211648944 ; + mps.get()[ 48 ] = 0.11471063502833712 ; + mps.get()[ 49 ] = -0.17128708246811702 ; + mps.get()[ 50 ] = -0.537166251460361 ; + mps.get()[ 51 ] = 0.2131135551453959 ; + mps.get()[ 52 ] = -0.19584141695097979 ; + mps.get()[ 53 ] = -0.5224748182520113 ; + mps.get()[ 54 ] = -0.4786314439048336 ; + mps.get()[ 55 ] = -0.28829738309774056 ; + mps.get()[ 64 ] = -0.22760229053474473 ; + mps.get()[ 65 ] = -0.3983913913209793 ; + mps.get()[ 66 ] = 0.02677088226132468 ; + mps.get()[ 67 ] = 0.019402378895274214 ; + mps.get()[ 68 ] = 0.11824715374271104 ; + mps.get()[ 69 ] = -0.6255924221100192 ; + mps.get()[ 70 ] = 0.4373827784232901 ; + mps.get()[ 71 ] = 0.43787715330879845 ; + mps.get()[ 80 ] = 0.46930961473374244 ; + mps.get()[ 81 ] = 0.5404386307552339 ; + mps.get()[ 82 ] = -0.4215827008929379 ; + mps.get()[ 83 ] = 0.07792743601226776 ; + mps.get()[ 84 ] = -0.17872276471126872 ; + mps.get()[ 85 ] = -0.05634054845076332 ; + mps.get()[ 86 ] = 0.31229453092671483 ; + mps.get()[ 87 ] = 0.41379458050793116 ; + mps.get()[ 168 ] = 1.0 ; + mps.get()[ 169 ] = 0.0 ; + mps.get()[ 186 ] = 1.0 ; + mps.get()[ 187 ] = 0.0 ; + mps.get()[ 204 ] = 1.0 ; + mps.get()[ 205 ] = 0.0 ; + mps.get()[ 222 ] = 1.0 ; + mps.get()[ 223 ] = 0.0 ; + mps.get()[ 288 ] = -0.6130799737070379 ; + mps.get()[ 289 ] = 0.0 ; + mps.get()[ 290 ] = 0.043268104759918435 ; + mps.get()[ 291 ] = -0.18995272475290723 ; + mps.get()[ 292 ] = 0.12876152804781352 ; + mps.get()[ 293 ] = -0.11893213425423177 ; + mps.get()[ 294 ] = 0.1607817034199853 ; + mps.get()[ 295 ] = 0.24665465943552506 ; + mps.get()[ 296 ] = 0.24552427823440562 ; + mps.get()[ 297 ] = 0.03609285713030893 ; + mps.get()[ 298 ] = -0.15990808712940063 ; + mps.get()[ 299 ] = 0.24468178722817494 ; + mps.get()[ 300 ] = -0.4914494834254487 ; + mps.get()[ 301 ] = 0.2405398049739527 ; + mps.get()[ 302 ] = -0.14134232805639232 ; + mps.get()[ 303 ] = 0.0487940490917071 ; + mps.get()[ 304 ] = 0.2742172608377126 ; + mps.get()[ 305 ] = 0.047271896662111734 ; + mps.get()[ 306 ] = -0.18146376283725354 ; + mps.get()[ 307 ] = 0.33152462391237286 ; + mps.get()[ 308 ] = 0.0773807177545771 ; + mps.get()[ 309 ] = 0.24654528023213645 ; + mps.get()[ 310 ] = 0.008528550130968378 ; + mps.get()[ 311 ] = 0.2390239731739813 ; + mps.get()[ 312 ] = -0.508089429071731 ; + mps.get()[ 313 ] = -0.002320091211748876 ; + mps.get()[ 314 ] = -0.13528872019886337 ; + mps.get()[ 315 ] = 0.31045800372692844 ; + mps.get()[ 316 ] = -0.3746798814674866 ; + mps.get()[ 317 ] = 0.1374707983071416 ; + mps.get()[ 318 ] = 0.06279287873849984 ; + mps.get()[ 319 ] = 0.345950035760249 ; + mps.get()[ 320 ] = 0.2170489335583332 ; + mps.get()[ 321 ] = 0.0 ; + mps.get()[ 322 ] = 0.25343359465176174 ; + mps.get()[ 323 ] = -0.06460873268181125 ; + mps.get()[ 324 ] = 0.46101262974278245 ; + mps.get()[ 325 ] = -0.2092480984031435 ; + mps.get()[ 326 ] = 0.13091963057724038 ; + mps.get()[ 327 ] = 0.22386537991270267 ; + mps.get()[ 328 ] = 0.12356459122286649 ; + mps.get()[ 329 ] = -0.4070091557135954 ; + mps.get()[ 330 ] = -0.28135621185555465 ; + mps.get()[ 331 ] = -0.3875888286693259 ; + mps.get()[ 332 ] = 0.03359145124758722 ; + mps.get()[ 333 ] = 0.08762316323030826 ; + mps.get()[ 334 ] = 0.2470684092385266 ; + mps.get()[ 335 ] = 0.2841719777265282 ; + mps.get()[ 336 ] = -0.10369449090740858 ; + mps.get()[ 337 ] = -0.06989422362389419 ; + mps.get()[ 338 ] = 0.22469512904806252 ; + mps.get()[ 339 ] = -0.5302042447544824 ; + mps.get()[ 340 ] = 0.1385544129616299 ; + mps.get()[ 341 ] = 0.16964924283127805 ; + mps.get()[ 342 ] = -0.04065543762807928 ; + mps.get()[ 343 ] = 0.005959393071539496 ; + mps.get()[ 344 ] = -0.4512829757314996 ; + mps.get()[ 345 ] = -0.2586701038412787 ; + mps.get()[ 346 ] = 0.5529069902589456 ; + mps.get()[ 347 ] = -0.03606848742855439 ; + mps.get()[ 348 ] = -0.14118940851816178 ; + mps.get()[ 349 ] = -0.06994728084240831 ; + mps.get()[ 350 ] = 0.018957416881223405 ; + mps.get()[ 351 ] = 0.01819725521128998 ; + mps.get()[ 352 ] = -0.17686033082431762 ; + mps.get()[ 353 ] = 0.0 ; + mps.get()[ 354 ] = 0.2931301972780771 ; + mps.get()[ 355 ] = 0.21016759261710286 ; + mps.get()[ 356 ] = -0.11356014183124072 ; + mps.get()[ 357 ] = -0.46047703337338425 ; + mps.get()[ 358 ] = -0.13658537870586535 ; + mps.get()[ 359 ] = -0.40395680360094305 ; + mps.get()[ 360 ] = -0.2192834055419525 ; + mps.get()[ 361 ] = 0.1147739015484587 ; + mps.get()[ 362 ] = 0.0406334021199655 ; + mps.get()[ 363 ] = -0.13324853969745315 ; + mps.get()[ 364 ] = -0.08677952965904745 ; + mps.get()[ 365 ] = -0.06520136749772663 ; + mps.get()[ 366 ] = -0.2592315135086059 ; + mps.get()[ 367 ] = 0.5217332205571495 ; + mps.get()[ 368 ] = 0.04262419252580863 ; + mps.get()[ 369 ] = 0.3848508087888552 ; + mps.get()[ 370 ] = 0.07107418272505975 ; + mps.get()[ 371 ] = -0.23184566218302685 ; + mps.get()[ 372 ] = 0.2521398405505697 ; + mps.get()[ 373 ] = 0.07367621634957493 ; + mps.get()[ 374 ] = 0.35237066854683313 ; + mps.get()[ 375 ] = -0.037975076688907206 ; + mps.get()[ 376 ] = 0.04017444083340625 ; + mps.get()[ 377 ] = 0.3474071346882127 ; + mps.get()[ 378 ] = -0.09328244237604688 ; + mps.get()[ 379 ] = 0.254631755111914 ; + mps.get()[ 380 ] = 0.11980261826920166 ; + mps.get()[ 381 ] = -0.5763161558192667 ; + mps.get()[ 382 ] = 0.03761704118581086 ; + mps.get()[ 383 ] = 0.23001403035505844 ; + mps.get()[ 384 ] = -0.26919429961914343 ; + mps.get()[ 385 ] = 0.0 ; + mps.get()[ 386 ] = -0.26974433859093544 ; + mps.get()[ 387 ] = 0.07475159568942476 ; + mps.get()[ 388 ] = 0.4614017944048309 ; + mps.get()[ 389 ] = -0.1653283114243778 ; + mps.get()[ 390 ] = -0.5969489482357646 ; + mps.get()[ 391 ] = -0.010072342322176322 ; + mps.get()[ 392 ] = -0.0019403159041931585 ; + mps.get()[ 393 ] = -0.07573380596911085 ; + mps.get()[ 394 ] = 0.05771235935096261 ; + mps.get()[ 395 ] = 0.32234604777657144 ; + mps.get()[ 396 ] = 0.28055767387417235 ; + mps.get()[ 397 ] = -0.08179104024696918 ; + mps.get()[ 398 ] = 0.22420649690508931 ; + mps.get()[ 399 ] = 0.06214434558530708 ; + mps.get()[ 400 ] = -0.10312646494528237 ; + mps.get()[ 401 ] = -0.46866618040645364 ; + mps.get()[ 402 ] = -0.3880666432855699 ; + mps.get()[ 403 ] = -0.03681826251267426 ; + mps.get()[ 404 ] = -0.25192564830049524 ; + mps.get()[ 405 ] = -0.024048027190332867 ; + mps.get()[ 406 ] = 0.2710542945659806 ; + mps.get()[ 407 ] = 0.22315379238735505 ; + mps.get()[ 408 ] = 0.1774445513167211 ; + mps.get()[ 409 ] = -0.08929584369397156 ; + mps.get()[ 410 ] = 0.2513518619982434 ; + mps.get()[ 411 ] = 0.010901632944735585 ; + mps.get()[ 412 ] = 0.20937810155968847 ; + mps.get()[ 413 ] = -0.14207394095443068 ; + mps.get()[ 414 ] = 0.09108907016995436 ; + mps.get()[ 415 ] = 0.5053457101186702 ; + mps.get()[ 544 ] = -0.23436455 ; + mps.get()[ 545 ] = 0.53141874 ; + mps.get()[ 546 ] = 0.06564701 ; + mps.get()[ 547 ] = -0.016569737 ; + mps.get()[ 560 ] = 0.27552164 ; + mps.get()[ 561 ] = 0.39324737 ; + mps.get()[ 562 ] = 0.03920218 ; + mps.get()[ 563 ] = 0.3126193 ; + mps.get()[ 576 ] = 0.08090294 ; + mps.get()[ 577 ] = 0.067396805 ; + mps.get()[ 578 ] = 0.38391852 ; + mps.get()[ 579 ] = 0.49181914 ; + mps.get()[ 592 ] = -0.34309888 ; + mps.get()[ 593 ] = -0.16689296 ; + mps.get()[ 594 ] = 0.3111027 ; + mps.get()[ 595 ] = 0.33973938 ; + mps.get()[ 608 ] = -0.39236507 ; + mps.get()[ 609 ] = 0.21032207 ; + mps.get()[ 610 ] = 0.084636666 ; + mps.get()[ 611 ] = -0.026731271 ; + mps.get()[ 624 ] = 0.048989747 ; + mps.get()[ 625 ] = -0.22122668 ; + mps.get()[ 626 ] = 0.24929003 ; + mps.get()[ 627 ] = -0.23605682 ; + mps.get()[ 640 ] = 0.02890851 ; + mps.get()[ 641 ] = -0.008860635 ; + mps.get()[ 642 ] = -0.30513528 ; + mps.get()[ 643 ] = 0.14362136 ; + mps.get()[ 656 ] = 0.008236099 ; + mps.get()[ 657 ] = 0.15793985 ; + mps.get()[ 658 ] = 0.16013248 ; + mps.get()[ 659 ] = -0.17186542 ; + mps.get()[ 672 ] = 2.1042667e-32 ; + mps.get()[ 673 ] = -1.7309414e-32 ; + mps.get()[ 674 ] = -1.18279285e-32 ; + mps.get()[ 675 ] = 4.1110057e-33 ; + mps.get()[ 688 ] = 1.6622225e-33 ; + mps.get()[ 689 ] = 1.718814e-32 ; + mps.get()[ 690 ] = -1.2495627e-32 ; + mps.get()[ 691 ] = 1.3456783e-32 ; + mps.get()[ 704 ] = -1.5954751e-32 ; + mps.get()[ 705 ] = 1.0564825e-32 ; + mps.get()[ 706 ] = 8.3999555e-33 ; + mps.get()[ 707 ] = 7.0071443e-34 ; + mps.get()[ 720 ] = 3.413174e-34 ; + mps.get()[ 721 ] = -1.01854646e-32 ; + mps.get()[ 722 ] = 1.0789845e-32 ; + mps.get()[ 723 ] = -5.4311017e-33 ; + mps.get()[ 800 ] = 0.503408 ; + mps.get()[ 801 ] = 0.0 ; + mps.get()[ 802 ] = -0.48754588 ; + mps.get()[ 803 ] = 0.38097852 ; + mps.get()[ 804 ] = -0.4678266 ; + mps.get()[ 805 ] = 0.0 ; + mps.get()[ 806 ] = -0.29991406 ; + mps.get()[ 807 ] = 0.2343591 ; + + // The below snippets compute the following: + // | + // +---+ +---+ +---+ +---+ +---+ + // mps2 | 0 +-+ 1 +-+ 2 +-+ 3 |-+ 4 | + // +-+-+ +-+-+ +-+-+ +-+-+ +-+-+ + // | | | | + // | | | | + // +-+-+ +-+-+ +-+-+ +-+-+ +-+-+ + // mps | 0 +-+ 1 +-+ 2 +-+ 3 |-+ 4 | + // +---+ +---+ +---+ +---+ +---+ + // | + //----------------------------------------- + // | + // +---+ +---+ +---+ +---+ +---+ + // mps2 | 0 +-+ 1 +-+ 2 +-+ 3 |-+ 4 | + // +-+-+ +-+-+ +-+-+ +-+-+ +-+-+ + // | | | | + // | | | | + // +-+-+ +-+-+ +-+-+ +-+-+ +-+-+ + // mps | 0 +-+ 1 +-+ 2 +-+ 3 |-+ 4 | + // +---+ +---+ +---+ +---+ +---+ + // | + //----------------------------------------- + // And so on. + + float *rdm = new float[8]; + ss.ReduceDensityMatrix(mps, scratch, 0, rdm); + EXPECT_NEAR(rdm[ 0 ], 0.43434495 , 1e-4); + EXPECT_NEAR(rdm[ 1 ], 0.0 , 1e-4); + EXPECT_NEAR(rdm[ 2 ], 0.0452075 , 1e-4); + EXPECT_NEAR(rdm[ 3 ], -0.06079552 , 1e-4); + EXPECT_NEAR(rdm[ 4 ], 0.0452075 , 1e-4); + EXPECT_NEAR(rdm[ 5 ], 0.06079552 , 1e-4); + EXPECT_NEAR(rdm[ 6 ], 0.5656554 , 1e-4); + EXPECT_NEAR(rdm[ 7 ], 0.0 , 1e-4); + ss.ReduceDensityMatrix(mps, scratch, 1, rdm); + EXPECT_NEAR(rdm[ 0 ], 0.39097905 , 1e-4); + EXPECT_NEAR(rdm[ 1 ], 0.0 , 1e-4); + EXPECT_NEAR(rdm[ 2 ], 0.02212441 , 1e-4); + EXPECT_NEAR(rdm[ 3 ], -0.026811063 , 1e-4); + EXPECT_NEAR(rdm[ 4 ], 0.02212441 , 1e-4); + EXPECT_NEAR(rdm[ 5 ], 0.026811063 , 1e-4); + EXPECT_NEAR(rdm[ 6 ], 0.6090213 , 1e-4); + EXPECT_NEAR(rdm[ 7 ], 0.0 , 1e-4); + ss.ReduceDensityMatrix(mps, scratch, 2, rdm); + EXPECT_NEAR(rdm[ 0 ], 0.49911368 , 1e-4); + EXPECT_NEAR(rdm[ 1 ], 0.0 , 1e-4); + EXPECT_NEAR(rdm[ 2 ], -0.1224697 , 1e-4); + EXPECT_NEAR(rdm[ 3 ], 0.030501436 , 1e-4); + EXPECT_NEAR(rdm[ 4 ], -0.1224697 , 1e-4); + EXPECT_NEAR(rdm[ 5 ], -0.030501436 , 1e-4); + EXPECT_NEAR(rdm[ 6 ], 0.5008867 , 1e-4); + EXPECT_NEAR(rdm[ 7 ], 0.0 , 1e-4); + ss.ReduceDensityMatrix(mps, scratch, 3, rdm); + EXPECT_NEAR(rdm[ 0 ], 0.5358647 , 1e-4); + EXPECT_NEAR(rdm[ 1 ], 0.0 , 1e-4); + EXPECT_NEAR(rdm[ 2 ], 0.11097979 , 1e-4); + EXPECT_NEAR(rdm[ 3 ], 0.08869759 , 1e-4); + EXPECT_NEAR(rdm[ 4 ], 0.11097979 , 1e-4); + EXPECT_NEAR(rdm[ 5 ], -0.08869759 , 1e-4); + EXPECT_NEAR(rdm[ 6 ], 0.4641356 , 1e-4); + EXPECT_NEAR(rdm[ 7 ], 0.0 , 1e-4); + ss.ReduceDensityMatrix(mps, scratch, 4, rdm); + EXPECT_NEAR(rdm[ 0 ], 0.47228184 , 1e-4); + EXPECT_NEAR(rdm[ 1 ], 0.0 , 1e-4); + EXPECT_NEAR(rdm[ 2 ], -0.105126746 , 1e-4); + EXPECT_NEAR(rdm[ 3 ], -0.082148254 , 1e-4); + EXPECT_NEAR(rdm[ 4 ], -0.105126746 , 1e-4); + EXPECT_NEAR(rdm[ 5 ], 0.082148254 , 1e-4); + EXPECT_NEAR(rdm[ 6 ], 0.5277185 , 1e-4); + EXPECT_NEAR(rdm[ 7 ], 0.0 , 1e-4); + delete[](rdm); + +} + } // namespace } // namespace mps } // namespace qsim From 793712ca1047d920929b1efbcd0bae72245ebbea Mon Sep 17 00:00:00 2001 From: Michael Broughton Date: Tue, 7 Dec 2021 14:37:58 -0800 Subject: [PATCH 179/246] Sergei feedback. --- lib/mps_statespace.h | 30 +++++++++++++----------------- tests/mps_statespace_test.cc | 3 +-- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/lib/mps_statespace.h b/lib/mps_statespace.h index 1ca77906..acdf69db 100644 --- a/lib/mps_statespace.h +++ b/lib/mps_statespace.h @@ -234,7 +234,7 @@ class MPSStateSpace { } // Compute the 2x2 1-RDM of state on index. Result written to rdm. - // Requires: scratch to be allocated. + // Requires: scratch and rdm to be allocated. static void ReduceDensityMatrix(MPS& state, MPS& scratch, int index, fp_type* rdm) { const auto num_qubits = state.num_qubits(); @@ -245,6 +245,9 @@ class MPSStateSpace { auto offset = 0; fp_type* state_raw = state.get(); fp_type* scratch_raw = scratch.get(); + fp_type* state_raw_workspace = state_raw + end + 2 * bond_dim * bond_dim; + fp_type* scratch_raw_workspace = + scratch_raw + end + 2 * bond_dim * bond_dim; Copy(state, scratch); @@ -252,9 +255,8 @@ class MPSStateSpace { ConstMatrixMap top((Complex*)scratch_raw, 2, bond_dim); ConstMatrixMap bot((Complex*)state_raw, 2, bond_dim); MatrixMap partial_contract((Complex*)(state_raw + end), bond_dim, bond_dim); - MatrixMap partial_contract2( - (Complex*)(state_raw + end + 2 * bond_dim * bond_dim), bond_dim, - 2 * bond_dim); + MatrixMap partial_contract2((Complex*)(state_raw_workspace), bond_dim, + 2 * bond_dim); partial_contract.setZero(); partial_contract(0, 0) = 1; @@ -268,8 +270,7 @@ class MPSStateSpace { // reshape: new (&partial_contract2) - MatrixMap((Complex*)(state_raw + end + 2 * bond_dim * bond_dim), - bond_dim, 2 * bond_dim); + MatrixMap((Complex*)(state_raw_workspace), bond_dim, 2 * bond_dim); // Merge bot into left boundary merged tensor. new (&bot) ConstMatrixMap((Complex*)(state_raw + offset), bond_dim, @@ -278,8 +279,7 @@ class MPSStateSpace { // reshape: new (&partial_contract2) - MatrixMap((Complex*)(state_raw + end + 2 * bond_dim * bond_dim), - 2 * bond_dim, bond_dim); + MatrixMap((Complex*)(state_raw_workspace), 2 * bond_dim, bond_dim); // Merge top into partial_contract2. new (&top) ConstMatrixMap((Complex*)(scratch_raw + offset), 2 * bond_dim, @@ -296,8 +296,7 @@ class MPSStateSpace { new (&partial_contract) MatrixMap((Complex*)(scratch_raw + end), bond_dim, bond_dim); new (&partial_contract2) - MatrixMap((Complex*)(scratch_raw + end + 2 * bond_dim * bond_dim), - bond_dim, 2 * bond_dim); + MatrixMap((Complex*)(scratch_raw_workspace), bond_dim, 2 * bond_dim); partial_contract.setZero(); partial_contract(0, 0) = 1; @@ -305,13 +304,12 @@ class MPSStateSpace { partial_contract.noalias() = top * bot.adjoint(); } - for (unsigned i = num_qubits - 2; i > index; i--) { + for (unsigned i = num_qubits - 2; i > index; --i) { offset = GetBlockOffset(state, i); // reshape: new (&partial_contract2) - MatrixMap((Complex*)(scratch_raw + end + 2 * bond_dim * bond_dim), - 2 * bond_dim, bond_dim); + MatrixMap((Complex*)(scratch_raw_workspace), 2 * bond_dim, bond_dim); // Merge bot into left boundary merged tensor. new (&bot) ConstMatrixMap((Complex*)(state_raw + offset), 2 * bond_dim, @@ -320,8 +318,7 @@ class MPSStateSpace { // reshape: new (&partial_contract2) - MatrixMap((Complex*)(scratch_raw + end + 2 * bond_dim * bond_dim), - bond_dim, 2 * bond_dim); + MatrixMap((Complex*)(scratch_raw_workspace), bond_dim, 2 * bond_dim); // Merge top into partial_contract2. new (&top) ConstMatrixMap((Complex*)(scratch_raw + offset), bond_dim, @@ -342,8 +339,7 @@ class MPSStateSpace { new (&top) ConstMatrixMap((Complex*)(state_raw + offset), bond_dim, 2 * right_dim); new (&partial_contract2) - MatrixMap((Complex*)(scratch_raw + end + 2 * bond_dim * bond_dim), - bond_dim, 2 * right_dim); + MatrixMap((Complex*)(scratch_raw_workspace), bond_dim, 2 * right_dim); partial_contract2.noalias() = partial_contract * top.conjugate(); // copy the bottom contraction scratch_raw to state_raw to save space. memcpy(state_raw + end, scratch_raw + end, diff --git a/tests/mps_statespace_test.cc b/tests/mps_statespace_test.cc index 4f86154d..190f08df 100644 --- a/tests/mps_statespace_test.cc +++ b/tests/mps_statespace_test.cc @@ -851,7 +851,7 @@ TEST(MPSStateSpaceTest, ReduceDensityMatrixLarge){ //----------------------------------------- // And so on. - float *rdm = new float[8]; + float rdm[8]; ss.ReduceDensityMatrix(mps, scratch, 0, rdm); EXPECT_NEAR(rdm[ 0 ], 0.43434495 , 1e-4); EXPECT_NEAR(rdm[ 1 ], 0.0 , 1e-4); @@ -897,7 +897,6 @@ TEST(MPSStateSpaceTest, ReduceDensityMatrixLarge){ EXPECT_NEAR(rdm[ 5 ], 0.082148254 , 1e-4); EXPECT_NEAR(rdm[ 6 ], 0.5277185 , 1e-4); EXPECT_NEAR(rdm[ 7 ], 0.0 , 1e-4); - delete[](rdm); } From 02392f489e96201f16fec12be2daf9b0e95cf6a8 Mon Sep 17 00:00:00 2001 From: Matthew Neeley Date: Thu, 16 Dec 2021 09:12:02 -0800 Subject: [PATCH 180/246] Add url to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 2fc70507..121b4dad 100644 --- a/setup.py +++ b/setup.py @@ -90,6 +90,7 @@ def build_extension(self, ext): setup( name="qsimcirq", version=__version__, + url="https://github.com/quantumlib/qsim", author="Vamsi Krishna Devabathini", author_email="devabathini92@gmail.com", python_requires=">=3.3.0", From f3d63cbd93acc5b1e666908cf0522f5806c98ed6 Mon Sep 17 00:00:00 2001 From: Michael Broughton Date: Fri, 17 Dec 2021 02:32:21 -0800 Subject: [PATCH 181/246] Adds working bitstring Sample function to MPS. --- lib/mps_statespace.h | 64 +++++++++++ tests/mps_statespace_test.cc | 204 +++++++++++++++++++++++++++++++++++ 2 files changed, 268 insertions(+) diff --git a/lib/mps_statespace.h b/lib/mps_statespace.h index acdf69db..641d0332 100644 --- a/lib/mps_statespace.h +++ b/lib/mps_statespace.h @@ -25,7 +25,9 @@ #include #include #include +#include #include +#include #include "../eigen/Eigen/Dense" #include "../eigen/unsupported/Eigen/CXX11/Tensor" @@ -372,6 +374,68 @@ class MPSStateSpace { out = t_4d.contract(t_2d, product_dims); } + // Draw a single bitstring sample from state using scratch and scratch2 + // as working space. + static void SampleOnce(MPS& state, MPS& scratch, MPS& scratch2, + std::mt19937* random_gen, std::vector* sample) { + const auto bond_dim = state.bond_dim(); + const auto num_qubits = state.num_qubits(); + std::default_random_engine generator; + fp_type* scratch_raw = scratch.get(); + fp_type rdm[8]; + + sample->reserve(num_qubits); + Copy(state, scratch); + Copy(state, scratch2); + + // Sample left block. + ReduceDensityMatrix(scratch, scratch2, 0, rdm); + auto p0 = rdm[0] / (rdm[0] + rdm[6]); + std::bernoulli_distribution distribution(1 - p0); + auto bit_val = distribution(*random_gen); + + sample->push_back(bit_val); + MatrixMap tensor_block((Complex*)scratch_raw, 2, bond_dim); + tensor_block.row(!bit_val).setZero(); + tensor_block.imag() *= -1; + + // Sample internal blocks. + for (unsigned i = 1; i < num_qubits - 1; i++) { + ReduceDensityMatrix(scratch, scratch2, i, rdm); + p0 = rdm[0] / (rdm[0] + rdm[6]); + distribution = std::bernoulli_distribution(1 - p0); + bit_val = distribution(*random_gen); + + sample->push_back(bit_val); + const auto mem_start = GetBlockOffset(scratch, i); + new (&tensor_block) MatrixMap((Complex*)(scratch_raw + mem_start), + bond_dim * 2, bond_dim); + for (unsigned j = !bit_val; j < 2 * bond_dim; j += 2) { + tensor_block.row(j).setZero(); + } + tensor_block.imag() *= -1; + } + + // Sample right block. + ReduceDensityMatrix(scratch, scratch2, num_qubits - 1, rdm); + p0 = rdm[0] / (rdm[0] + rdm[6]); + distribution = std::bernoulli_distribution(1 - p0); + bit_val = distribution(*random_gen); + sample->push_back(bit_val); + } + + // Draw num_samples bitstring samples from state and store the result + // bit vectors in results. Uses scratch and scratch2 as workspace. + static void Sample(MPS& state, MPS& scratch, MPS& scratch2, + unsigned num_samples, unsigned seed, + std::vector>* results) { + std::mt19937 rand_source(seed); + results->reserve(num_samples); + for (unsigned i = 0; i < num_samples; i++) { + SampleOnce(state, scratch, scratch2, &rand_source, &(*results)[i]); + } + } + // Testing only. Convert the MPS to a wavefunction under "normal" ordering. // Requires: wf be allocated beforehand with bond_dim * 2 ^ num_qubits -1 // memory. diff --git a/tests/mps_statespace_test.cc b/tests/mps_statespace_test.cc index 190f08df..28c92ed3 100644 --- a/tests/mps_statespace_test.cc +++ b/tests/mps_statespace_test.cc @@ -16,6 +16,7 @@ #include "../lib/formux.h" #include "gtest/gtest.h" +#include "gmock/gmock.h" namespace qsim { @@ -900,6 +901,209 @@ TEST(MPSStateSpaceTest, ReduceDensityMatrixLarge){ } +TEST(MPSStateSpaceTest, SampleOnceSimple){ + auto ss = MPSStateSpace(1); + auto mps = ss.Create(3, 4); + auto scratch = ss.Create(3, 4); + auto scratch2 = ss.Create(3, 4); + std::mt19937 rand_source(1234); + std::vector results; + + // Set to |100>. + results.clear(); + ss.SetStateZero(mps); + mps.get()[0] = 0; + mps.get()[8] = 1; + ss.SampleOnce(mps, scratch, scratch2, &rand_source, &results); + ASSERT_THAT(results, testing::ElementsAre(1, 0, 0)); + //EXPECT_EQ(1,2); + + // Set to |010>. + results.clear(); + ss.SetStateZero(mps); + mps.get()[16] = 0; + mps.get()[24] = 1; + ss.SampleOnce(mps, scratch, scratch2, &rand_source, &results); + ASSERT_THAT(results, testing::ElementsAre(0, 1, 0)); + + // Set to |001>. + results.clear(); + ss.SetStateZero(mps); + mps.get()[80] = 0; + mps.get()[82] = 1; + ss.SampleOnce(mps, scratch, scratch2, &rand_source, &results); + ASSERT_THAT(results, testing::ElementsAre(0, 0, 1)); + + // Set to |101>. + results.clear(); + ss.SetStateZero(mps); + mps.get()[0] = 0; + mps.get()[8] = 1; + mps.get()[80] = 0; + mps.get()[82] = 1; + ss.SampleOnce(mps, scratch, scratch2, &rand_source, &results); + ASSERT_THAT(results, testing::ElementsAre(1, 0, 1)); +} + +TEST(MPSStateSpaceTest, SampleGHZ){ + const int num_samples = 10000; + auto ss = MPSStateSpace(1); + auto mps = ss.Create(3, 4); + auto scratch = ss.Create(3, 4); + auto scratch2 = ss.Create(3, 4); + std::vector> results( + num_samples, std::vector({})); + + memset(mps.get(), 0, ss.RawSize(mps)); + mps.get()[0] = 1; + mps.get()[10] = 1; + mps.get()[16] = 1; + mps.get()[42] = -1; + mps.get()[80] = 0.70710677; + mps.get()[86] = -0.70710677; + + float count = 0; + ss.Sample(mps, scratch, scratch2, num_samples, 1234, &results); + for(int i = 0 ; i < num_samples; i++){ + ASSERT_THAT(results[i], testing::AnyOf(testing::ElementsAre(1, 1, 1), + testing::ElementsAre(0, 0, 0))); + count += results[i][0]; + } + EXPECT_NEAR(count / float(num_samples), 0.5, 1e-2); +} + +TEST(MPSStateSpaceTest, SampleComplex){ + const int num_samples = 10000; + auto ss = MPSStateSpace(1); + auto mps = ss.Create(4, 4); + auto scratch = ss.Create(4, 4); + auto scratch2 = ss.Create(4, 4); + std::vector> results( + num_samples, std::vector({})); + + memset(mps.get(), 0, ss.RawSize(mps)); + mps.get()[ 0 ] = 0.033688569334715854 ; + mps.get()[ 1 ] = -0.10444182602180123 ; + mps.get()[ 2 ] = 0.9076354671683359 ; + mps.get()[ 3 ] = 0.405160344657187 ; + mps.get()[ 8 ] = -0.9595253512026178 ; + mps.get()[ 9 ] = -0.25936097827312377 ; + mps.get()[ 10 ] = -0.03987001675676861 ; + mps.get()[ 11 ] = 0.10224185693597321 ; + mps.get()[ 16 ] = -0.4350591822776815 ; + mps.get()[ 17 ] = 0.22228546667942578 ; + mps.get()[ 18 ] = -0.6285732819602607 ; + mps.get()[ 19 ] = 0.5943422063507785 ; + mps.get()[ 20 ] = -0.02428345908884816 ; + mps.get()[ 21 ] = 0.026256572727652475 ; + mps.get()[ 22 ] = 0.0728063572325396 ; + mps.get()[ 23 ] = -0.07991114142962712 ; + mps.get()[ 24 ] = -0.1642035571020447 ; + mps.get()[ 25 ] = -0.8209212529030018 ; + mps.get()[ 26 ] = 0.21124207331921135 ; + mps.get()[ 27 ] = 0.4033152234452636 ; + mps.get()[ 28 ] = -0.11315780332634073 ; + mps.get()[ 29 ] = -0.18477947087021204 ; + mps.get()[ 30 ] = -0.11199707215175961 ; + mps.get()[ 31 ] = -0.17985444082650426 ; + mps.get()[ 32 ] = -0.18059162771674087 ; + mps.get()[ 33 ] = -0.12173196101857839 ; + mps.get()[ 34 ] = 0.19817171168239098 ; + mps.get()[ 35 ] = 0.063054719070231 ; + mps.get()[ 36 ] = 0.45024015745008505 ; + mps.get()[ 37 ] = 0.11068157212593255 ; + mps.get()[ 38 ] = 0.8106501683288581 ; + mps.get()[ 39 ] = 0.19287240226762353 ; + mps.get()[ 40 ] = -0.08702392413741208 ; + mps.get()[ 41 ] = 0.07370887245848737 ; + mps.get()[ 42 ] = -0.018412987786278347 ; + mps.get()[ 43 ] = 0.027921764369775018 ; + mps.get()[ 44 ] = 0.42351439662329743 ; + mps.get()[ 45 ] = -0.7466201917698305 ; + mps.get()[ 46 ] = -0.24298735917837008 ; + mps.get()[ 47 ] = 0.4359199055764641 ; + mps.get()[ 80 ] = 0.422436255430577 ; + mps.get()[ 81 ] = 0.0 ; + mps.get()[ 82 ] = 0.1211402132186689 ; + mps.get()[ 83 ] = -0.819174648113452 ; + mps.get()[ 84 ] = 0.0 ; + mps.get()[ 85 ] = -7.333691512826885e-20 ; + mps.get()[ 88 ] = -0.8676720638499252 ; + mps.get()[ 89 ] = 0.1360568551008419 ; + mps.get()[ 90 ] = 0.011793867549118184 ; + mps.get()[ 91 ] = -0.44097834083157716 ; + mps.get()[ 96 ] = -0.08978879818674973 ; + mps.get()[ 97 ] = 0.0 ; + mps.get()[ 98 ] = -0.021807957717091198 ; + mps.get()[ 99 ] = -0.05873893136151775 ; + mps.get()[ 100 ] = 0.0 ; + mps.get()[ 101 ] = -9.89518074266081e-19 ; + mps.get()[ 104 ] = 0.14307979940517454 ; + mps.get()[ 105 ] = 0.06032194765563529 ; + mps.get()[ 106 ] = 0.22589440044405648 ; + mps.get()[ 107 ] = -0.2397609424987549 ; + mps.get()[ 112 ] = 0.12734430206944722 ; + mps.get()[ 113 ] = 0.0 ; + mps.get()[ 114 ] = -0.003114595079760157 ; + mps.get()[ 115 ] = 0.06816683893204967 ; + mps.get()[ 116 ] = 2.722010100011512e-17 ; + mps.get()[ 117 ] = 1.9629880528929172e-18 ; + mps.get()[ 120 ] = 0.014022255715263434 ; + mps.get()[ 121 ] = 0.017127855001478075 ; + mps.get()[ 122 ] = 0.025812082320798548 ; + mps.get()[ 123 ] = -0.027110021000464 ; + mps.get()[ 128 ] = -0.018262196707018574 ; + mps.get()[ 129 ] = 0.0 ; + mps.get()[ 130 ] = 0.0032725358458428836 ; + mps.get()[ 131 ] = 0.0310845568816579 ; + mps.get()[ 132 ] = 1.935877805637811e-17 ; + mps.get()[ 133 ] = -1.0370773958773989e-18 ; + mps.get()[ 136 ] = -0.028632305212090994 ; + mps.get()[ 137 ] = 0.012199896816087576 ; + mps.get()[ 138 ] = 0.0009323445588941451 ; + mps.get()[ 139 ] = -0.014212789540748644 ; + mps.get()[ 144 ] = -0.07762944756130831 ; + mps.get()[ 145 ] = -0.25063255485414937 ; + mps.get()[ 146 ] = 0.515385895406013 ; + mps.get()[ 147 ] = 0.7314486807404007 ; + mps.get()[ 148 ] = 0.20689214104052 ; + mps.get()[ 149 ] = 0.2781707321332216 ; + mps.get()[ 150 ] = 0.08286244916183945 ; + mps.get()[ 151 ] = 0.05888783848647657 ; + + ss.Sample(mps, scratch, scratch2, num_samples, 12345, &results); + + std::vector expected({ + 0.00467637, + 0.0020386, + 0.00112952, + 0.0269848, + 0.00704221, + 0.00147802, + 0.00243688, + 0.0350753, + 0.0324814, + 0.0141599, + 0.0412371, + 0.0780275, + 0.0644363, + 0.140995, + 0.0355866, + 0.512215, + }); + std::vector hist(16, 0); + for(int i =0;i Date: Fri, 17 Dec 2021 02:35:52 -0800 Subject: [PATCH 182/246] remove iostream include. --- lib/mps_statespace.h | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/mps_statespace.h b/lib/mps_statespace.h index 641d0332..c017e650 100644 --- a/lib/mps_statespace.h +++ b/lib/mps_statespace.h @@ -25,7 +25,6 @@ #include #include #include -#include #include #include From fa36fd74e8baafec61d1944c961ed5e902633bd3 Mon Sep 17 00:00:00 2001 From: Michael Broughton Date: Fri, 17 Dec 2021 13:08:26 -0800 Subject: [PATCH 183/246] remove use of gmock. --- tests/mps_statespace_test.cc | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/mps_statespace_test.cc b/tests/mps_statespace_test.cc index 28c92ed3..9473eaf9 100644 --- a/tests/mps_statespace_test.cc +++ b/tests/mps_statespace_test.cc @@ -16,7 +16,6 @@ #include "../lib/formux.h" #include "gtest/gtest.h" -#include "gmock/gmock.h" namespace qsim { @@ -915,8 +914,9 @@ TEST(MPSStateSpaceTest, SampleOnceSimple){ mps.get()[0] = 0; mps.get()[8] = 1; ss.SampleOnce(mps, scratch, scratch2, &rand_source, &results); - ASSERT_THAT(results, testing::ElementsAre(1, 0, 0)); - //EXPECT_EQ(1,2); + EXPECT_EQ(results[0], 1); + EXPECT_EQ(results[1], 0); + EXPECT_EQ(results[2], 0); // Set to |010>. results.clear(); @@ -924,7 +924,9 @@ TEST(MPSStateSpaceTest, SampleOnceSimple){ mps.get()[16] = 0; mps.get()[24] = 1; ss.SampleOnce(mps, scratch, scratch2, &rand_source, &results); - ASSERT_THAT(results, testing::ElementsAre(0, 1, 0)); + EXPECT_EQ(results[0], 0); + EXPECT_EQ(results[1], 1); + EXPECT_EQ(results[2], 0); // Set to |001>. results.clear(); @@ -932,7 +934,9 @@ TEST(MPSStateSpaceTest, SampleOnceSimple){ mps.get()[80] = 0; mps.get()[82] = 1; ss.SampleOnce(mps, scratch, scratch2, &rand_source, &results); - ASSERT_THAT(results, testing::ElementsAre(0, 0, 1)); + EXPECT_EQ(results[0], 0); + EXPECT_EQ(results[1], 0); + EXPECT_EQ(results[2], 1); // Set to |101>. results.clear(); @@ -942,7 +946,9 @@ TEST(MPSStateSpaceTest, SampleOnceSimple){ mps.get()[80] = 0; mps.get()[82] = 1; ss.SampleOnce(mps, scratch, scratch2, &rand_source, &results); - ASSERT_THAT(results, testing::ElementsAre(1, 0, 1)); + EXPECT_EQ(results[0], 1); + EXPECT_EQ(results[1], 0); + EXPECT_EQ(results[2], 1); } TEST(MPSStateSpaceTest, SampleGHZ){ @@ -965,8 +971,10 @@ TEST(MPSStateSpaceTest, SampleGHZ){ float count = 0; ss.Sample(mps, scratch, scratch2, num_samples, 1234, &results); for(int i = 0 ; i < num_samples; i++){ - ASSERT_THAT(results[i], testing::AnyOf(testing::ElementsAre(1, 1, 1), - testing::ElementsAre(0, 0, 0))); + bool all_same = 1; + all_same &= results[i][0] == results[i][1]; + all_same &= results[i][1] == results[i][2]; + EXPECT_EQ(all_same, 1); count += results[i][0]; } EXPECT_NEAR(count / float(num_samples), 0.5, 1e-2); From 70f7a2bfe24a65635efa63f41514c3ddb5efbf03 Mon Sep 17 00:00:00 2001 From: Michael Broughton Date: Tue, 21 Dec 2021 04:21:19 -0800 Subject: [PATCH 184/246] Moved to O(n) sampling algorithm. --- lib/mps_statespace.h | 153 ++++++++++++++++++++++--- tests/mps_statespace_test.cc | 210 ++++++++++++++++++----------------- 2 files changed, 243 insertions(+), 120 deletions(-) diff --git a/lib/mps_statespace.h b/lib/mps_statespace.h index c017e650..721b626e 100644 --- a/lib/mps_statespace.h +++ b/lib/mps_statespace.h @@ -22,9 +22,11 @@ #include #endif +#include #include #include #include +#include #include #include @@ -324,7 +326,7 @@ class MPSStateSpace { // Merge top into partial_contract2. new (&top) ConstMatrixMap((Complex*)(scratch_raw + offset), bond_dim, 2 * bond_dim); - // [bd, bd] = [2bd, bd] @ [bd, 2bd] + // [bd, bd] = [bd, 2bd] @ [bd, 2bd] partial_contract.noalias() = top * partial_contract2.adjoint(); } @@ -377,46 +379,165 @@ class MPSStateSpace { // as working space. static void SampleOnce(MPS& state, MPS& scratch, MPS& scratch2, std::mt19937* random_gen, std::vector* sample) { + // TODO: carefully profile with perf and optimize temp storage + // locations for cache friendliness. const auto bond_dim = state.bond_dim(); const auto num_qubits = state.num_qubits(); + const auto end = Size(state); + const auto left_frontier_offset = GetBlockOffset(state, num_qubits + 1); std::default_random_engine generator; + fp_type* state_raw = state.get(); fp_type* scratch_raw = scratch.get(); + fp_type* scratch2_raw = scratch2.get(); fp_type rdm[8]; sample->reserve(num_qubits); Copy(state, scratch); Copy(state, scratch2); - // Sample left block. - ReduceDensityMatrix(scratch, scratch2, 0, rdm); + // Store prefix contractions in scratch2. + auto offset = GetBlockOffset(state, num_qubits - 1); + ConstMatrixMap top((Complex*)(state_raw + offset), bond_dim, 2); + ConstMatrixMap bot((Complex*)(scratch_raw + offset), bond_dim, 2); + MatrixMap partial_contract((Complex*)(scratch2_raw + offset), bond_dim, + bond_dim); + MatrixMap partial_contract2((Complex*)(scratch_raw + end), bond_dim, + 2 * bond_dim); + partial_contract.noalias() = top * bot.adjoint(); + + for (unsigned i = num_qubits - 2; i > 0; --i) { + offset = GetBlockOffset(state, i); + // reshape: + new (&partial_contract2) + MatrixMap((Complex*)(scratch_raw + end), 2 * bond_dim, bond_dim); + + // Merge bot into left boundary merged tensor. + new (&bot) ConstMatrixMap((Complex*)(scratch_raw + offset), 2 * bond_dim, + bond_dim); + partial_contract2.noalias() = bot * partial_contract.adjoint(); + + // reshape: + new (&partial_contract2) + MatrixMap((Complex*)(scratch_raw + end), bond_dim, 2 * bond_dim); + + // Merge top into partial_contract2. + new (&top) ConstMatrixMap((Complex*)(state_raw + offset), bond_dim, + 2 * bond_dim); + + // merge into partial_contract -> scracth2_raw. + new (&partial_contract) + MatrixMap((Complex*)(scratch2_raw + offset), bond_dim, bond_dim); + partial_contract.noalias() = top * partial_contract2.adjoint(); + } + + // Compute RDM-0 and draw first sample. + offset = GetBlockOffset(state, 1); + new (&top) ConstMatrixMap((Complex*)state_raw, 2, bond_dim); + new (&bot) ConstMatrixMap((Complex*)scratch_raw, 2, bond_dim); + new (&partial_contract) + MatrixMap((Complex*)(scratch2_raw + offset), bond_dim, bond_dim); + new (&partial_contract2) + MatrixMap((Complex*)(scratch_raw + end), 2, bond_dim); + + partial_contract2.noalias() = bot * partial_contract.adjoint(); + + new (&partial_contract) MatrixMap((Complex*)rdm, 2, 2); + partial_contract.noalias() = top * partial_contract2.adjoint(); auto p0 = rdm[0] / (rdm[0] + rdm[6]); std::bernoulli_distribution distribution(1 - p0); auto bit_val = distribution(*random_gen); - sample->push_back(bit_val); - MatrixMap tensor_block((Complex*)scratch_raw, 2, bond_dim); - tensor_block.row(!bit_val).setZero(); - tensor_block.imag() *= -1; - // Sample internal blocks. + // collapse state. + new (&partial_contract) MatrixMap((Complex*)scratch_raw, 2, bond_dim); + partial_contract.row(!bit_val).setZero(); + + // Prepare left contraction frontier. + new (&partial_contract2) MatrixMap( + (Complex*)(scratch2_raw + left_frontier_offset), bond_dim, bond_dim); + partial_contract2.noalias() = + partial_contract.transpose() * partial_contract.conjugate(); + + // Compute RDM-i and draw internal tensor samples. for (unsigned i = 1; i < num_qubits - 1; i++) { - ReduceDensityMatrix(scratch, scratch2, i, rdm); + // Get leftmost [bd, bd] contraction and contract with top. + offset = GetBlockOffset(state, i); + new (&partial_contract) MatrixMap( + (Complex*)(scratch2_raw + left_frontier_offset), bond_dim, bond_dim); + new (&top) ConstMatrixMap((Complex*)(state_raw + offset), bond_dim, + 2 * bond_dim); + new (&partial_contract2) + MatrixMap((Complex*)(state_raw + end), bond_dim, 2 * bond_dim); + partial_contract2.noalias() = partial_contract * top.conjugate(); + + // Contract top again for correct shape. + MatrixMap partial_contract3((Complex*)(scratch_raw + end), 2 * bond_dim, + 2 * bond_dim); + partial_contract3.noalias() = top.transpose() * partial_contract2; + + // Conduct final tensor contraction operations. Cannot be easily compiled + // to matmul. Perf reports shows only ~6% of runtime spent here on large + // systems. + offset = GetBlockOffset(state, i + 1); + const Eigen::TensorMap> + t_4d((Complex*)(scratch_raw + end), 2, bond_dim, 2, bond_dim); + const Eigen::TensorMap> + t_2d((Complex*)(scratch2_raw + offset), bond_dim, bond_dim); + + const Eigen::array, 2> product_dims = { + Eigen::IndexPair(1, 0), + Eigen::IndexPair(3, 1), + }; + Eigen::TensorMap> out( + (Complex*)rdm, 2, 2); + out = t_4d.contract(t_2d, product_dims); + + // Sample bit and collapse state. p0 = rdm[0] / (rdm[0] + rdm[6]); distribution = std::bernoulli_distribution(1 - p0); bit_val = distribution(*random_gen); sample->push_back(bit_val); - const auto mem_start = GetBlockOffset(scratch, i); - new (&tensor_block) MatrixMap((Complex*)(scratch_raw + mem_start), - bond_dim * 2, bond_dim); + offset = GetBlockOffset(state, i); + new (&partial_contract) + MatrixMap((Complex*)(scratch_raw + offset), bond_dim * 2, bond_dim); for (unsigned j = !bit_val; j < 2 * bond_dim; j += 2) { - tensor_block.row(j).setZero(); + partial_contract.row(j).setZero(); } - tensor_block.imag() *= -1; + + // Update left frontier. + new (&partial_contract) MatrixMap( + (Complex*)(scratch2_raw + left_frontier_offset), bond_dim, bond_dim); + + // reshape: + new (&partial_contract2) + MatrixMap((Complex*)(state_raw + end), bond_dim, 2 * bond_dim); + + // Merge bot into left boundary merged tensor. + new (&bot) ConstMatrixMap((Complex*)(scratch_raw + offset), bond_dim, + 2 * bond_dim); + partial_contract2.noalias() = partial_contract * bot.conjugate(); + + // reshape: + new (&partial_contract2) + MatrixMap((Complex*)(state_raw + end), 2 * bond_dim, bond_dim); + + // Merge top into partial_contract2. + new (&top) ConstMatrixMap((Complex*)(scratch_raw + offset), 2 * bond_dim, + bond_dim); + partial_contract.noalias() = top.transpose() * partial_contract2; } - // Sample right block. - ReduceDensityMatrix(scratch, scratch2, num_qubits - 1, rdm); + // Compute RDM-(n-1) and sample. + offset = GetBlockOffset(state, num_qubits - 1); + new (&partial_contract2) + MatrixMap((Complex*)(state_raw + end), bond_dim, 2); + + new (&top) ConstMatrixMap((Complex*)(state_raw + offset), bond_dim, 2); + partial_contract2.noalias() = partial_contract * top.conjugate(); + new (&partial_contract) MatrixMap((Complex*)rdm, 2, 2); + partial_contract.noalias() = top.transpose() * partial_contract2; + p0 = rdm[0] / (rdm[0] + rdm[6]); distribution = std::bernoulli_distribution(1 - p0); bit_val = distribution(*random_gen); diff --git a/tests/mps_statespace_test.cc b/tests/mps_statespace_test.cc index 9473eaf9..1ea037c5 100644 --- a/tests/mps_statespace_test.cc +++ b/tests/mps_statespace_test.cc @@ -976,6 +976,7 @@ TEST(MPSStateSpaceTest, SampleGHZ){ all_same &= results[i][1] == results[i][2]; EXPECT_EQ(all_same, 1); count += results[i][0]; + EXPECT_EQ(results[i].size(), 3); } EXPECT_NEAR(count / float(num_samples), 0.5, 1e-2); } @@ -990,126 +991,127 @@ TEST(MPSStateSpaceTest, SampleComplex){ num_samples, std::vector({})); memset(mps.get(), 0, ss.RawSize(mps)); - mps.get()[ 0 ] = 0.033688569334715854 ; - mps.get()[ 1 ] = -0.10444182602180123 ; - mps.get()[ 2 ] = 0.9076354671683359 ; - mps.get()[ 3 ] = 0.405160344657187 ; - mps.get()[ 8 ] = -0.9595253512026178 ; - mps.get()[ 9 ] = -0.25936097827312377 ; - mps.get()[ 10 ] = -0.03987001675676861 ; - mps.get()[ 11 ] = 0.10224185693597321 ; - mps.get()[ 16 ] = -0.4350591822776815 ; - mps.get()[ 17 ] = 0.22228546667942578 ; - mps.get()[ 18 ] = -0.6285732819602607 ; - mps.get()[ 19 ] = 0.5943422063507785 ; - mps.get()[ 20 ] = -0.02428345908884816 ; - mps.get()[ 21 ] = 0.026256572727652475 ; - mps.get()[ 22 ] = 0.0728063572325396 ; - mps.get()[ 23 ] = -0.07991114142962712 ; - mps.get()[ 24 ] = -0.1642035571020447 ; - mps.get()[ 25 ] = -0.8209212529030018 ; - mps.get()[ 26 ] = 0.21124207331921135 ; - mps.get()[ 27 ] = 0.4033152234452636 ; - mps.get()[ 28 ] = -0.11315780332634073 ; - mps.get()[ 29 ] = -0.18477947087021204 ; - mps.get()[ 30 ] = -0.11199707215175961 ; - mps.get()[ 31 ] = -0.17985444082650426 ; - mps.get()[ 32 ] = -0.18059162771674087 ; - mps.get()[ 33 ] = -0.12173196101857839 ; - mps.get()[ 34 ] = 0.19817171168239098 ; - mps.get()[ 35 ] = 0.063054719070231 ; - mps.get()[ 36 ] = 0.45024015745008505 ; - mps.get()[ 37 ] = 0.11068157212593255 ; - mps.get()[ 38 ] = 0.8106501683288581 ; - mps.get()[ 39 ] = 0.19287240226762353 ; - mps.get()[ 40 ] = -0.08702392413741208 ; - mps.get()[ 41 ] = 0.07370887245848737 ; - mps.get()[ 42 ] = -0.018412987786278347 ; - mps.get()[ 43 ] = 0.027921764369775018 ; - mps.get()[ 44 ] = 0.42351439662329743 ; - mps.get()[ 45 ] = -0.7466201917698305 ; - mps.get()[ 46 ] = -0.24298735917837008 ; - mps.get()[ 47 ] = 0.4359199055764641 ; - mps.get()[ 80 ] = 0.422436255430577 ; + mps.get()[ 0 ] = -0.4917038696869799 ; + mps.get()[ 1 ] = 0.016731957658280873 ; + mps.get()[ 2 ] = 0.86132663373237 ; + mps.get()[ 3 ] = 0.12674293823327035 ; + mps.get()[ 8 ] = -0.5023020703950029 ; + mps.get()[ 9 ] = -0.711083648814302 ; + mps.get()[ 10 ] = -0.20727818303023368 ; + mps.get()[ 11 ] = -0.4461932766843352 ; + mps.get()[ 16 ] = 0.15655121570640956 ; + mps.get()[ 17 ] = 0.4732738079187066 ; + mps.get()[ 18 ] = -0.08511634068671248 ; + mps.get()[ 19 ] = 0.4509108800471812 ; + mps.get()[ 20 ] = 0.3399824326377983 ; + mps.get()[ 21 ] = 0.26456637633430585 ; + mps.get()[ 22 ] = 0.5923848721836553 ; + mps.get()[ 23 ] = -0.06659540240231236 ; + mps.get()[ 24 ] = 0.3386920440520109 ; + mps.get()[ 25 ] = -0.5078386788732782 ; + mps.get()[ 26 ] = -0.5938438138167242 ; + mps.get()[ 27 ] = -0.2253530600030204 ; + mps.get()[ 28 ] = -0.08439705180650249 ; + mps.get()[ 29 ] = 0.18289872169116567 ; + mps.get()[ 30 ] = 0.33989833066754255 ; + mps.get()[ 31 ] = -0.2604753706869852 ; + mps.get()[ 32 ] = 0.3013840839514031 ; + mps.get()[ 33 ] = -0.10757629710841352 ; + mps.get()[ 34 ] = -0.043855659850960294 ; + mps.get()[ 35 ] = -0.0999497956398576 ; + mps.get()[ 36 ] = 0.6336147397284169 ; + mps.get()[ 37 ] = 0.43658807519265264 ; + mps.get()[ 38 ] = -0.448346536528476 ; + mps.get()[ 39 ] = 0.30428652791930944 ; + mps.get()[ 40 ] = 0.2954131683108271 ; + mps.get()[ 41 ] = -0.4349910681437736 ; + mps.get()[ 42 ] = 0.35640542464599323 ; + mps.get()[ 43 ] = 0.4970533197510696 ; + mps.get()[ 44 ] = -0.37101487814696105 ; + mps.get()[ 45 ] = 0.2100308254832807 ; + mps.get()[ 46 ] = 0.10591704897593116 ; + mps.get()[ 47 ] = 0.3955295090226334 ; + mps.get()[ 80 ] = -0.24953341864058454 ; mps.get()[ 81 ] = 0.0 ; - mps.get()[ 82 ] = 0.1211402132186689 ; - mps.get()[ 83 ] = -0.819174648113452 ; - mps.get()[ 84 ] = 0.0 ; - mps.get()[ 85 ] = -7.333691512826885e-20 ; - mps.get()[ 88 ] = -0.8676720638499252 ; - mps.get()[ 89 ] = 0.1360568551008419 ; - mps.get()[ 90 ] = 0.011793867549118184 ; - mps.get()[ 91 ] = -0.44097834083157716 ; - mps.get()[ 96 ] = -0.08978879818674973 ; + mps.get()[ 82 ] = -0.5480093086703182 ; + mps.get()[ 83 ] = -0.20497358945530025 ; + mps.get()[ 84 ] = -1.1887516198406813e-16 ; + mps.get()[ 85 ] = 3.714848812002129e-18 ; + mps.get()[ 88 ] = 0.6045663379213811 ; + mps.get()[ 89 ] = -0.3501271865840065 ; + mps.get()[ 90 ] = -0.29968140886676936 ; + mps.get()[ 91 ] = 0.40493683779718603 ; + mps.get()[ 96 ] = 0.3073334814703704 ; mps.get()[ 97 ] = 0.0 ; - mps.get()[ 98 ] = -0.021807957717091198 ; - mps.get()[ 99 ] = -0.05873893136151775 ; - mps.get()[ 100 ] = 0.0 ; - mps.get()[ 101 ] = -9.89518074266081e-19 ; - mps.get()[ 104 ] = 0.14307979940517454 ; - mps.get()[ 105 ] = 0.06032194765563529 ; - mps.get()[ 106 ] = 0.22589440044405648 ; - mps.get()[ 107 ] = -0.2397609424987549 ; - mps.get()[ 112 ] = 0.12734430206944722 ; + mps.get()[ 98 ] = 0.07297353820052123 ; + mps.get()[ 99 ] = -0.2859132301813451 ; + mps.get()[ 100 ] = -1.7214471606144266e-16 ; + mps.get()[ 101 ] = 5.379522376920083e-18 ; + mps.get()[ 104 ] = -0.18689238699414557 ; + mps.get()[ 105 ] = -0.4911602105890581 ; + mps.get()[ 106 ] = -0.30326863844349566 ; + mps.get()[ 107 ] = -0.22667282775953723 ; + mps.get()[ 112 ] = -0.10881711525857803 ; mps.get()[ 113 ] = 0.0 ; - mps.get()[ 114 ] = -0.003114595079760157 ; - mps.get()[ 115 ] = 0.06816683893204967 ; - mps.get()[ 116 ] = 2.722010100011512e-17 ; - mps.get()[ 117 ] = 1.9629880528929172e-18 ; - mps.get()[ 120 ] = 0.014022255715263434 ; - mps.get()[ 121 ] = 0.017127855001478075 ; - mps.get()[ 122 ] = 0.025812082320798548 ; - mps.get()[ 123 ] = -0.027110021000464 ; - mps.get()[ 128 ] = -0.018262196707018574 ; + mps.get()[ 114 ] = -0.146152770590198 ; + mps.get()[ 115 ] = 0.2149415742117364 ; + mps.get()[ 116 ] = -4.72314539505504e-16 ; + mps.get()[ 117 ] = 1.1519866817207415e-17 ; + mps.get()[ 120 ] = -0.01567698028444534 ; + mps.get()[ 121 ] = 0.013440646849502781 ; + mps.get()[ 122 ] = -0.17367051562799563 ; + mps.get()[ 123 ] = -0.24954843447516284 ; + mps.get()[ 128 ] = 0.24030153622040965 ; mps.get()[ 129 ] = 0.0 ; - mps.get()[ 130 ] = 0.0032725358458428836 ; - mps.get()[ 131 ] = 0.0310845568816579 ; - mps.get()[ 132 ] = 1.935877805637811e-17 ; - mps.get()[ 133 ] = -1.0370773958773989e-18 ; - mps.get()[ 136 ] = -0.028632305212090994 ; - mps.get()[ 137 ] = 0.012199896816087576 ; - mps.get()[ 138 ] = 0.0009323445588941451 ; - mps.get()[ 139 ] = -0.014212789540748644 ; - mps.get()[ 144 ] = -0.07762944756130831 ; - mps.get()[ 145 ] = -0.25063255485414937 ; - mps.get()[ 146 ] = 0.515385895406013 ; - mps.get()[ 147 ] = 0.7314486807404007 ; - mps.get()[ 148 ] = 0.20689214104052 ; - mps.get()[ 149 ] = 0.2781707321332216 ; - mps.get()[ 150 ] = 0.08286244916183945 ; - mps.get()[ 151 ] = 0.05888783848647657 ; + mps.get()[ 130 ] = -0.08309837568058188 ; + mps.get()[ 131 ] = 0.07924116582885271 ; + mps.get()[ 132 ] = -7.075275311738327e-17 ; + mps.get()[ 133 ] = 3.930708506521293e-18 ; + mps.get()[ 136 ] = 0.0725269370009367 ; + mps.get()[ 137 ] = 0.06123701427497634 ; + mps.get()[ 138 ] = -0.006630682493419155 ; + mps.get()[ 139 ] = 0.015491880670142021 ; + mps.get()[ 144 ] = -0.021403127627426542 ; + mps.get()[ 145 ] = 0.04422341855596844 ; + mps.get()[ 146 ] = 0.27602112861704176 ; + mps.get()[ 147 ] = 0.7790060986745896 ; + mps.get()[ 148 ] = 0.25252680029727903 ; + mps.get()[ 149 ] = 0.49967041792054084 ; + mps.get()[ 150 ] = -0.031679241045523554 ; + mps.get()[ 151 ] = -0.010202895067710558 ; ss.Sample(mps, scratch, scratch2, num_samples, 12345, &results); - std::vector expected({ - 0.00467637, - 0.0020386, - 0.00112952, - 0.0269848, - 0.00704221, - 0.00147802, - 0.00243688, - 0.0350753, - 0.0324814, - 0.0141599, - 0.0412371, - 0.0780275, - 0.0644363, - 0.140995, - 0.0355866, - 0.512215, + 0.036801, + 0.040697, + 0.002013, + 0.064595, + 0.014892, + 0.082028, + 0.008521, + 0.168310, + 0.022078, + 0.005907, + 0.024806, + 0.189074, + 0.090056, + 0.023125, + 0.116683, + 0.110406 }); std::vector hist(16, 0); for(int i =0;i Date: Tue, 21 Dec 2021 04:28:33 -0800 Subject: [PATCH 185/246] remove unused includes. --- lib/mps_statespace.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/mps_statespace.h b/lib/mps_statespace.h index 721b626e..9b3acf31 100644 --- a/lib/mps_statespace.h +++ b/lib/mps_statespace.h @@ -22,11 +22,9 @@ #include #endif -#include #include #include #include -#include #include #include From 04bac34fe6f130ee4c34ef22343ca3cc93d6bc9f Mon Sep 17 00:00:00 2001 From: igorpeshansky Date: Tue, 21 Dec 2021 19:22:40 -0500 Subject: [PATCH 186/246] Fix deprecated logging agent install script. See https://cloud.google.com/logging/docs/agent/installation for the latest installation instructions. --- .../multinode/terraform/htcondor/startup-centos.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh index bec6eb6c..7ec9572d 100644 --- a/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh +++ b/docs/tutorials/multinode/terraform/htcondor/startup-centos.sh @@ -101,8 +101,8 @@ eval $CONDOR_STARTUP_CMD ############################################################## # Install and configure logging agent for StackDriver ############################################################## -curl -sSO https://dl.google.com/cloudagents/install-logging-agent.sh -bash install-logging-agent.sh +curl -sSO https://dl.google.com/cloudagents/add-logging-agent-repo.sh +bash add-logging-agent-repo.sh --also-install # Install Custom Metric Plugin: google-fluentd-gem install fluent-plugin-stackdriver-monitoring @@ -186,4 +186,4 @@ fi # Now we can let everyone know that the setup is complete. -wall "******* HTCondor system configuration complete ********" \ No newline at end of file +wall "******* HTCondor system configuration complete ********" From 6460deb7e6fb9ec23372ffa0a582ae6ae83a4ccc Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Tue, 4 Jan 2022 15:59:08 +0100 Subject: [PATCH 187/246] Update fusers. --- lib/fuser_basic.h | 67 +++++++++++++++++------------ lib/fuser_mqubit.h | 87 ++++++++++++++++++++++---------------- tests/fuser_basic_test.cc | 35 +++++++++++++++ tests/fuser_mqubit_test.cc | 35 +++++++++++++++ 4 files changed, 161 insertions(+), 63 deletions(-) diff --git a/lib/fuser_basic.h b/lib/fuser_basic.h index abc006e8..39748bba 100644 --- a/lib/fuser_basic.h +++ b/lib/fuser_basic.h @@ -56,7 +56,7 @@ class BasicGateFuser final : public Fuser { * two-qubit gates will get fused. To respect specific time boundaries while * fusing gates, use the other version of this method below. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by 'gates'. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gates The gates (or pointers to the gates) to be fused. * Gate times of the gates that act on the same qubits should be ordered. * Gates that are out of time order should not cross the time boundaries @@ -66,18 +66,18 @@ class BasicGateFuser final : public Fuser { * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates(const Parameter& param, - unsigned num_qubits, + unsigned max_qubit1, const std::vector& gates, bool fuse_matrix = true) { return FuseGates( - param, num_qubits, gates.cbegin(), gates.cend(), {}, fuse_matrix); + param, max_qubit1, gates.cbegin(), gates.cend(), {}, fuse_matrix); } /** * Stores sets of gates that can be applied together. Only one- and * two-qubit gates will get fused. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by 'gates'. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gates The gates (or pointers to the gates) to be fused. * Gate times of the gates that act on the same qubits should be ordered. * Gates that are out of time order should not cross the time boundaries @@ -91,10 +91,10 @@ class BasicGateFuser final : public Fuser { */ static std::vector FuseGates( const Parameter& param, - unsigned num_qubits, const std::vector& gates, + unsigned max_qubit1, const std::vector& gates, const std::vector& times_to_split_at, bool fuse_matrix = true) { - return FuseGates(param, num_qubits, gates.cbegin(), gates.cend(), + return FuseGates(param, max_qubit1, gates.cbegin(), gates.cend(), times_to_split_at, fuse_matrix); } @@ -103,7 +103,7 @@ class BasicGateFuser final : public Fuser { * two-qubit gates will get fused. To respect specific time boundaries while * fusing gates, use the other version of this method below. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by gates. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gfirst, glast The iterator range [gfirst, glast) to fuse gates * (or pointers to gates) in. Gate times of the gates that act on the same * qubits should be ordered. Gates that are out of time order should not @@ -113,18 +113,18 @@ class BasicGateFuser final : public Fuser { * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates( - const Parameter& param, unsigned num_qubits, + const Parameter& param, unsigned max_qubit1, typename std::vector::const_iterator gfirst, typename std::vector::const_iterator glast, bool fuse_matrix = true) { - return FuseGates(param, num_qubits, gfirst, glast, {}, fuse_matrix); + return FuseGates(param, max_qubit1, gfirst, glast, {}, fuse_matrix); } /** * Stores sets of gates that can be applied together. Only one- and * two-qubit gates will get fused. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by gates. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gfirst, glast The iterator range [gfirst, glast) to fuse gates * (or pointers to gates) in. Gate times of the gates that act on the same * qubits should be ordered. Gates that are out of time order should not @@ -138,7 +138,7 @@ class BasicGateFuser final : public Fuser { * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates( - const Parameter& param, unsigned num_qubits, + const Parameter& param, unsigned max_qubit1, typename std::vector::const_iterator gfirst, typename std::vector::const_iterator glast, const std::vector& times_to_split_at, @@ -162,7 +162,7 @@ class BasicGateFuser final : public Fuser { std::vector gates_seq; // Lattice of gates: qubits "hyperplane" and time direction. - std::vector> gates_lat(num_qubits); + std::vector> gates_lat(max_qubit1); // Current unfused gate. auto gate_it = gfirst; @@ -171,7 +171,7 @@ class BasicGateFuser final : public Fuser { gates_seq.resize(0); gates_seq.reserve(num_gates); - for (unsigned k = 0; k < num_qubits; ++k) { + for (unsigned k = 0; k < max_qubit1; ++k) { gates_lat[k].resize(0); gates_lat[k].reserve(128); } @@ -182,9 +182,7 @@ class BasicGateFuser final : public Fuser { if (gate.time > times[l]) break; - if (GateIsOutOfOrder(gate.time, gate.qubits, gates_lat) - || GateIsOutOfOrder(gate.time, gate.controlled_by, gates_lat)) { - IO::errorf("gate is out of time order.\n"); + if (!ValidateGate(gate, max_qubit1, gates_lat)) { gates_fused.resize(0); return gates_fused; } @@ -193,7 +191,7 @@ class BasicGateFuser final : public Fuser { auto& mea_gates_at_time = measurement_gates[gate.time]; if (mea_gates_at_time.size() == 0) { gates_seq.push_back(&gate); - mea_gates_at_time.reserve(num_qubits); + mea_gates_at_time.reserve(max_qubit1); } mea_gates_at_time.push_back(&gate); @@ -217,7 +215,7 @@ class BasicGateFuser final : public Fuser { } } - std::vector last(num_qubits, 0); + std::vector last(max_qubit1, 0); const RGate* delayed_measurement_gate = nullptr; @@ -281,7 +279,7 @@ class BasicGateFuser final : public Fuser { } } - for (unsigned q = 0; q < num_qubits; ++q) { + for (unsigned q = 0; q < max_qubit1; ++q) { auto l = last[q]; if (l == gates_lat[q].size()) continue; @@ -360,17 +358,32 @@ class BasicGateFuser final : public Fuser { return k; } - template - static bool GateIsOutOfOrder(unsigned time, - const std::vector& qubits, - const GatesLat& gates_lat) { - for (unsigned q : qubits) { - if (!gates_lat[q].empty() && time <= gates_lat[q].back()->time) { - return true; + template + static bool ValidateGate(const Gate2& gate, unsigned max_qubit1, + const GatesLat& gates_lat) { + for (unsigned q : gate.qubits) { + if (q >= max_qubit1) { + IO::errorf("gate qubits are out of range.\n"); + return false; + } + if (!gates_lat[q].empty() && gate.time <= gates_lat[q].back()->time) { + IO::errorf("gate is out of time order.\n"); + return false; + } + } + + for (unsigned q : gate.controlled_by) { + if (q >= max_qubit1) { + IO::errorf("gate qubits are out of range.\n"); + return false; + } + if (!gates_lat[q].empty() && gate.time <= gates_lat[q].back()->time) { + IO::errorf("gate is out of time order.\n"); + return false; } } - return false; + return true; } }; diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index 08a40d11..31415653 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -156,7 +156,7 @@ class MultiQubitGateFuser final : public Fuser { * time boundaries while fusing gates, use the other version of this method * below. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by 'gates'. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gates The gates (or pointers to the gates) to be fused. * Gate times of the gates that act on the same qubits should be ordered. * Gates that are out of time order should not cross the time boundaries @@ -166,17 +166,17 @@ class MultiQubitGateFuser final : public Fuser { * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates(const Parameter& param, - unsigned num_qubits, + unsigned max_qubit1, const std::vector& gates, bool fuse_matrix = true) { return FuseGates( - param, num_qubits, gates.cbegin(), gates.cend(), {}, fuse_matrix); + param, max_qubit1, gates.cbegin(), gates.cend(), {}, fuse_matrix); } /** * Stores sets of gates that can be applied together. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by 'gates'. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gates The gates (or pointers to the gates) to be fused. * Gate times of the gates that act on the same qubits should be ordered. * Gates that are out of time order should not cross the time boundaries @@ -190,10 +190,10 @@ class MultiQubitGateFuser final : public Fuser { */ static std::vector FuseGates( const Parameter& param, - unsigned num_qubits, const std::vector& gates, + unsigned max_qubit1, const std::vector& gates, const std::vector& times_to_split_at, bool fuse_matrix = true) { - return FuseGates(param, num_qubits, gates.cbegin(), gates.cend(), + return FuseGates(param, max_qubit1, gates.cbegin(), gates.cend(), times_to_split_at, fuse_matrix); } @@ -202,7 +202,7 @@ class MultiQubitGateFuser final : public Fuser { * time boundaries while fusing gates, use the other version of this method * below. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by gates. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gfirst, glast The iterator range [gfirst, glast) to fuse gates * (or pointers to gates) in. Gate times of the gates that act on the same * qubits should be ordered. Gates that are out of time order should not @@ -212,17 +212,17 @@ class MultiQubitGateFuser final : public Fuser { * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates( - const Parameter& param, unsigned num_qubits, + const Parameter& param, unsigned max_qubit1, typename std::vector::const_iterator gfirst, typename std::vector::const_iterator glast, bool fuse_matrix = true) { - return FuseGates(param, num_qubits, gfirst, glast, {}, fuse_matrix); + return FuseGates(param, max_qubit1, gfirst, glast, {}, fuse_matrix); } /** * Stores sets of gates that can be applied together. * @param param Options for gate fusion. - * @param num_qubits The number of qubits acted on by gates. + * @param max_qubit1 The maximum qubit index (plus one) acted on by 'gates'. * @param gfirst, glast The iterator range [gfirst, glast) to fuse gates * (or pointers to gates) in. Gate times of the gates that act on the same * qubits should be ordered. Gates that are out of time order should not @@ -236,7 +236,7 @@ class MultiQubitGateFuser final : public Fuser { * acting on a specific pair of qubits which can be applied as a group. */ static std::vector FuseGates( - const Parameter& param, unsigned num_qubits, + const Parameter& param, unsigned max_qubit1, typename std::vector::const_iterator gfirst, typename std::vector::const_iterator glast, const std::vector& times_to_split_at, @@ -253,7 +253,7 @@ class MultiQubitGateFuser final : public Fuser { auto epochs = Base::MergeWithMeasurementTimes(gfirst, glast, times_to_split_at); - LinkManager link_manager(num_qubits * num_gates); + LinkManager link_manager(max_qubit1 * num_gates); // Auxillary data structures. // Sequence of intermediate fused gates. @@ -261,10 +261,10 @@ class MultiQubitGateFuser final : public Fuser { // Gate "lattice". std::vector gates_lat; // Sequences of intermediate fused gates ordered by gate size. - std::vector> fgates(num_qubits + 1); + std::vector> fgates(max_qubit1 + 1); gates_seq.reserve(num_gates); - gates_lat.reserve(num_qubits); + gates_lat.reserve(max_qubit1); Scratch scratch; @@ -277,10 +277,10 @@ class MultiQubitGateFuser final : public Fuser { scratch.stack.reserve(8); Stat stat; - stat.num_gates.resize(num_qubits + 1, 0); + stat.num_gates.resize(max_qubit1 + 1, 0); unsigned max_fused_size = std::min(unsigned{6}, param.max_fused_size); - max_fused_size = std::min(max_fused_size, num_qubits); + max_fused_size = std::min(max_fused_size, max_qubit1); auto gate_it = gfirst; @@ -288,9 +288,9 @@ class MultiQubitGateFuser final : public Fuser { for (std::size_t l = 0; l < epochs.size(); ++l) { gates_seq.resize(0); gates_lat.resize(0); - gates_lat.resize(num_qubits, nullptr); + gates_lat.resize(max_qubit1, nullptr); - for (unsigned i = 0; i <= num_qubits; ++i) { + for (unsigned i = 0; i <= max_qubit1; ++i) { fgates[i].resize(0); } @@ -303,9 +303,7 @@ class MultiQubitGateFuser final : public Fuser { if (gate.time > epochs[l]) break; - if (GateIsOutOfOrder(gate.time, gate.qubits, gates_lat) - || GateIsOutOfOrder(gate.time, gate.controlled_by, gates_lat)) { - IO::errorf("gate is out of time order.\n"); + if (!ValidateGate(gate, max_qubit1, gates_lat)) { fused_gates.resize(0); return fused_gates; } @@ -320,8 +318,8 @@ class MultiQubitGateFuser final : public Fuser { gates_seq.push_back({&gate, {}, {}, {}, 0, kMeaCnt}); last_mea_gate = &gates_seq.back(); - last_mea_gate->qubits.reserve(num_qubits); - last_mea_gate->links.reserve(num_qubits); + last_mea_gate->qubits.reserve(max_qubit1); + last_mea_gate->links.reserve(max_qubit1); ++stat.num_fused_mea_gates; } @@ -394,12 +392,12 @@ class MultiQubitGateFuser final : public Fuser { if (max_fused_size > 2) { FuseGateSequences( - max_fused_size, num_qubits, scratch, gates_seq, stat, fused_gates); + max_fused_size, max_qubit1, scratch, gates_seq, stat, fused_gates); } else { unsigned prev_time = 0; std::vector orphaned_gates; - orphaned_gates.reserve(num_qubits); + orphaned_gates.reserve(max_qubit1); for (auto& fgate : gates_seq) { if (fgate.gates.size() == 0) continue; @@ -484,13 +482,13 @@ class MultiQubitGateFuser final : public Fuser { // // max_fused_size = 6: _-_-_ static void FuseGateSequences(unsigned max_fused_size, - unsigned num_qubits, Scratch& scratch, + unsigned max_qubit1, Scratch& scratch, std::vector& gates_seq, Stat& stat, std::vector& fused_gates) { unsigned prev_time = 0; std::vector orphaned_gates; - orphaned_gates.reserve(num_qubits); + orphaned_gates.reserve(max_qubit1); for (auto& fgate : gates_seq) { if (prev_time != fgate.parent->time) { @@ -1031,7 +1029,7 @@ class MultiQubitGateFuser final : public Fuser { if (verbosity < 5) return; IO::messagef("fused gate qubits:\n"); - for (const auto g : fused_gates) { + for (const auto& g : fused_gates) { IO::messagef("%6u ", g.parent->time); if (g.parent->kind == gate::kMeasurement) { IO::messagef("m"); @@ -1052,17 +1050,34 @@ class MultiQubitGateFuser final : public Fuser { } } - template - static bool GateIsOutOfOrder(unsigned time, - const std::vector& qubits, - const GatesLat& gates_lat) { - for (unsigned q : qubits) { - if (gates_lat[q] != nullptr && time <= gates_lat[q]->val->parent->time) { - return true; + template + static bool ValidateGate(const Gate2& gate, unsigned max_qubit1, + const GatesLat& gates_lat) { + for (unsigned q : gate.qubits) { + if (q >= max_qubit1) { + IO::errorf("gate qubits are out of range.\n"); + return false; + } + if (gates_lat[q] != nullptr + && gate.time <= gates_lat[q]->val->parent->time) { + IO::errorf("gate is out of time order.\n"); + return false; + } + } + + for (unsigned q : gate.controlled_by) { + if (q >= max_qubit1) { + IO::errorf("gate qubits are out of range.\n"); + return false; + } + if (gates_lat[q] != nullptr + && gate.time <= gates_lat[q]->val->parent->time) { + IO::errorf("gate is out of time order.\n"); + return false; } } - return false; + return true; } }; diff --git a/tests/fuser_basic_test.cc b/tests/fuser_basic_test.cc index 250d9f30..1eb85c28 100644 --- a/tests/fuser_basic_test.cc +++ b/tests/fuser_basic_test.cc @@ -1438,6 +1438,41 @@ TEST(FuserBasicTest, InvalidTimeOrder) { } } +TEST(FuserBasicTest, QubitsOutOfRange) { + using Gate = GateQSim; + using Fuser = BasicGateFuser; + + Fuser::Parameter param; + param.verbosity = 0; + + { + unsigned num_qubits = 3; + std::vector circuit = { + GateCZ::Create(0, 0, 3), + GateCZ::Create(0, 1, 2), + }; + + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end(), false); + + EXPECT_EQ(fused_gates.size(), 0); + } + + { + unsigned num_qubits = 3; + auto gate = GateZ::Create(0, 2); + std::vector circuit = { + GateCZ::Create(0, 0, 1), + MakeControlledGate({3}, gate), + }; + + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end(), false); + + EXPECT_EQ(fused_gates.size(), 0); + } +} + } // namespace qsim int main(int argc, char** argv) { diff --git a/tests/fuser_mqubit_test.cc b/tests/fuser_mqubit_test.cc index c41c6549..cf49b73b 100644 --- a/tests/fuser_mqubit_test.cc +++ b/tests/fuser_mqubit_test.cc @@ -982,6 +982,41 @@ TEST(FuserMultiQubitTest, InvalidTimeOrder) { } } +TEST(FuserMultiQubitTest, QubitsOutOfRange) { + using Fuser = MultiQubitGateFuser; + + Fuser::Parameter param; + param.verbosity = 0; + + { + unsigned num_qubits = 3; + std::vector circuit = { + CreateDummyGate(0, {0, 3}), + CreateDummyGate(0, {1, 2}), + }; + + param.max_fused_size = 2; + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end(), false); + + EXPECT_EQ(fused_gates.size(), 0); + } + + { + unsigned num_qubits = 3; + std::vector circuit = { + CreateDummyGate(0, {0, 1}), + CreateDummyControlledGate(0, {2}, {3}), + }; + + param.max_fused_size = 2; + auto fused_gates = Fuser::FuseGates( + param, num_qubits, circuit.begin(), circuit.end(), false); + + EXPECT_EQ(fused_gates.size(), 0); + } +} + TEST(FuserMultiQubitTest, OrphanedGates) { using Fuser = MultiQubitGateFuser; From 558a44643b37e82f5b77038c34e826f38dcb6358 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 4 Jan 2022 17:39:39 +0000 Subject: [PATCH 188/246] Update to dev version 2022-01-04 --- qsimcirq/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq/_version.py b/qsimcirq/_version.py index bf85e4ce..c95999d5 100644 --- a/qsimcirq/_version.py +++ b/qsimcirq/_version.py @@ -1,3 +1,3 @@ """The version number defined here is read automatically in setup.py.""" -__version__ = "0.11.1" +__version__ = "0.11.2.dev20220104" From 2b57cab96c1e5b415e68068169f12111c82a27e1 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Tue, 4 Jan 2022 19:19:37 +0100 Subject: [PATCH 189/246] Recommended fixes. --- lib/fuser_basic.h | 10 ++++++---- lib/fuser_mqubit.h | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/fuser_basic.h b/lib/fuser_basic.h index 39748bba..2fafb14a 100644 --- a/lib/fuser_basic.h +++ b/lib/fuser_basic.h @@ -363,22 +363,24 @@ class BasicGateFuser final : public Fuser { const GatesLat& gates_lat) { for (unsigned q : gate.qubits) { if (q >= max_qubit1) { - IO::errorf("gate qubits are out of range.\n"); + IO::errorf("fuser: gate qubit %u is out of range " + "(should be smaller than %u).\n", q, max_qubit1); return false; } if (!gates_lat[q].empty() && gate.time <= gates_lat[q].back()->time) { - IO::errorf("gate is out of time order.\n"); + IO::errorf("fuser: gate at time %u is out of time order.\n", gate.time); return false; } } for (unsigned q : gate.controlled_by) { if (q >= max_qubit1) { - IO::errorf("gate qubits are out of range.\n"); + IO::errorf("fuser: gate qubit %u is out of range " + "(should be smaller than %u).\n", q, max_qubit1); return false; } if (!gates_lat[q].empty() && gate.time <= gates_lat[q].back()->time) { - IO::errorf("gate is out of time order.\n"); + IO::errorf("fuser: gate at time %u is out of time order.\n", gate.time); return false; } } diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index 31415653..4ab4f100 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -1055,24 +1055,26 @@ class MultiQubitGateFuser final : public Fuser { const GatesLat& gates_lat) { for (unsigned q : gate.qubits) { if (q >= max_qubit1) { - IO::errorf("gate qubits are out of range.\n"); + IO::errorf("fuser: gate qubit %u is out of range " + "(should be smaller than %u).\n", q, max_qubit1); return false; } if (gates_lat[q] != nullptr && gate.time <= gates_lat[q]->val->parent->time) { - IO::errorf("gate is out of time order.\n"); + IO::errorf("fuser: gate at time %u is out of time order.\n", gate.time); return false; } } for (unsigned q : gate.controlled_by) { if (q >= max_qubit1) { - IO::errorf("gate qubits are out of range.\n"); + IO::errorf("fuser: gate qubit %u is out of range " + "(should be smaller than %u).\n", q, max_qubit1); return false; } if (gates_lat[q] != nullptr && gate.time <= gates_lat[q]->val->parent->time) { - IO::errorf("gate is out of time order.\n"); + IO::errorf("fuser: gate at time %u is out of time order.\n", gate.time); return false; } } From 91bc014a1bde878c286b50daaa4a2367df2959e0 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Wed, 5 Jan 2022 17:22:12 +0100 Subject: [PATCH 190/246] immintrin.h is not included in CUDA code. --- apps/qsim_amplitudes.cc | 1 + apps/qsim_base.cc | 2 +- apps/qsim_von_neumann.cc | 2 +- apps/qsimh_amplitudes.cc | 1 + apps/qsimh_base.cc | 1 + lib/BUILD | 9 ++++ lib/util.h | 21 --------- lib/util_cpu.h | 43 +++++++++++++++++++ pybind_interface/avx2/pybind_main_avx2.cpp | 1 + .../avx512/pybind_main_avx512.cpp | 1 + pybind_interface/basic/pybind_main_basic.cpp | 1 + pybind_interface/cuda/pybind_main_cuda.cpp | 3 ++ .../custatevec/pybind_main_custatevec.cpp | 3 ++ pybind_interface/pybind_main.cpp | 1 - pybind_interface/sse/pybind_main_sse.cpp | 1 + tests/BUILD | 2 +- tests/simulator_testfixture.h | 2 +- 17 files changed, 69 insertions(+), 26 deletions(-) create mode 100644 lib/util_cpu.h diff --git a/apps/qsim_amplitudes.cc b/apps/qsim_amplitudes.cc index 01d50b96..d37fdd6b 100644 --- a/apps/qsim_amplitudes.cc +++ b/apps/qsim_amplitudes.cc @@ -30,6 +30,7 @@ #include "../lib/run_qsim.h" #include "../lib/simmux.h" #include "../lib/util.h" +#include "../lib/util_cpu.h" constexpr char usage[] = "usage:\n ./qsim_amplitudes -c circuit_file " "-d times_to_save_results -i input_files " diff --git a/apps/qsim_base.cc b/apps/qsim_base.cc index 8d365e9a..5a7604d5 100644 --- a/apps/qsim_base.cc +++ b/apps/qsim_base.cc @@ -26,7 +26,7 @@ #include "../lib/io_file.h" #include "../lib/run_qsim.h" #include "../lib/simmux.h" -#include "../lib/util.h" +#include "../lib/util_cpu.h" constexpr char usage[] = "usage:\n ./qsim_base -c circuit -d maxtime " "-s seed -t threads -f max_fused_size " diff --git a/apps/qsim_von_neumann.cc b/apps/qsim_von_neumann.cc index c2264909..46576083 100644 --- a/apps/qsim_von_neumann.cc +++ b/apps/qsim_von_neumann.cc @@ -28,7 +28,7 @@ #include "../lib/io_file.h" #include "../lib/run_qsim.h" #include "../lib/simmux.h" -#include "../lib/util.h" +#include "../lib/util_cpu.h" constexpr char usage[] = "usage:\n ./qsim_von_neumann -c circuit -d maxtime " "-s seed -t threads -f max_fused_size " diff --git a/apps/qsimh_amplitudes.cc b/apps/qsimh_amplitudes.cc index cfb5b09a..7cb1b085 100644 --- a/apps/qsimh_amplitudes.cc +++ b/apps/qsimh_amplitudes.cc @@ -30,6 +30,7 @@ #include "../lib/run_qsimh.h" #include "../lib/simmux.h" #include "../lib/util.h" +#include "../lib/util_cpu.h" constexpr char usage[] = "usage:\n ./qsimh_amplitudes -c circuit_file " "-d maxtime -k part1_qubits " diff --git a/apps/qsimh_base.cc b/apps/qsimh_base.cc index c1656a8b..eb0c9c6c 100644 --- a/apps/qsimh_base.cc +++ b/apps/qsimh_base.cc @@ -30,6 +30,7 @@ #include "../lib/run_qsimh.h" #include "../lib/simmux.h" #include "../lib/util.h" +#include "../lib/util_cpu.h" constexpr char usage[] = "usage:\n ./qsimh_base -c circuit_file " "-d maximum_time -k part1_qubits " diff --git a/lib/BUILD b/lib/BUILD index d69ae1da..cc56dbd2 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -61,6 +61,7 @@ cc_library( "unitary_calculator_basic.h", "unitary_calculator_sse.h", "util.h", + "util_cpu.h", "vectorspace.h", ], ) @@ -123,6 +124,7 @@ cc_library( "unitary_calculator_basic.h", "unitary_calculator_sse.h", "util.h", + "util_cpu.h", "util_cuda.h", "vectorspace.h", "vectorspace_cuda.h", @@ -170,6 +172,7 @@ cc_library( "unitary_calculator_basic.h", "unitary_calculator_sse.h", "util.h", + "util_cpu.h", "vectorspace.h", ], ) @@ -208,6 +211,7 @@ cc_library( "statespace_basic.h", "statespace_sse.h", "util.h", + "util_cpu.h", "vectorspace.h", ], ) @@ -238,6 +242,11 @@ cc_library( hdrs = ["util.h"], ) +cc_library( + name = "util_cpu", + hdrs = ["util_cpu.h"], +) + # cuda_library cc_library( name = "util_cuda", diff --git a/lib/util.h b/lib/util.h index 933730c3..726a0192 100644 --- a/lib/util.h +++ b/lib/util.h @@ -15,10 +15,6 @@ #ifndef UTIL_H_ #define UTIL_H_ -#ifdef __SSE__ -# include -#endif - #include #include #include @@ -88,23 +84,6 @@ inline std::vector GenerateRandomValues( return rs; } -// This function sets flush-to-zero and denormals-are-zeros MXCSR control -// flags. This prevents rare cases of performance slowdown potentially at -// the cost of a tiny precision loss. -inline void SetFlushToZeroAndDenormalsAreZeros() { -#ifdef __SSE2__ - _mm_setcsr(_mm_getcsr() | 0x8040); -#endif -} - -// This function clears flush-to-zero and denormals-are-zeros MXCSR control -// flags. -inline void ClearFlushToZeroAndDenormalsAreZeros() { -#ifdef __SSE2__ - _mm_setcsr(_mm_getcsr() & ~unsigned{0x8040}); -#endif -} - } // namespace qsim #endif // UTIL_H_ diff --git a/lib/util_cpu.h b/lib/util_cpu.h new file mode 100644 index 00000000..8e024252 --- /dev/null +++ b/lib/util_cpu.h @@ -0,0 +1,43 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef UTIL_CPU_H_ +#define UTIL_CPU_H_ + +#ifdef __SSE2__ +# include +#endif + +namespace qsim { + +// This function sets flush-to-zero and denormals-are-zeros MXCSR control +// flags. This prevents rare cases of performance slowdown potentially at +// the cost of a tiny precision loss. +inline void SetFlushToZeroAndDenormalsAreZeros() { +#ifdef __SSE2__ + _mm_setcsr(_mm_getcsr() | 0x8040); +#endif +} + +// This function clears flush-to-zero and denormals-are-zeros MXCSR control +// flags. +inline void ClearFlushToZeroAndDenormalsAreZeros() { +#ifdef __SSE2__ + _mm_setcsr(_mm_getcsr() & ~unsigned{0x8040}); +#endif +} + +} // namespace qsim + +#endif // UTIL_CPU_H_ diff --git a/pybind_interface/avx2/pybind_main_avx2.cpp b/pybind_interface/avx2/pybind_main_avx2.cpp index 5729d163..1f7dc084 100644 --- a/pybind_interface/avx2/pybind_main_avx2.cpp +++ b/pybind_interface/avx2/pybind_main_avx2.cpp @@ -16,6 +16,7 @@ #include "../../lib/formux.h" #include "../../lib/simulator_avx.h" +#include "../../lib/util_cpu.h" namespace qsim { template diff --git a/pybind_interface/avx512/pybind_main_avx512.cpp b/pybind_interface/avx512/pybind_main_avx512.cpp index 0073a1e0..b87ececb 100644 --- a/pybind_interface/avx512/pybind_main_avx512.cpp +++ b/pybind_interface/avx512/pybind_main_avx512.cpp @@ -16,6 +16,7 @@ #include "../../lib/formux.h" #include "../../lib/simulator_avx512.h" +#include "../../lib/util_cpu.h" namespace qsim { template diff --git a/pybind_interface/basic/pybind_main_basic.cpp b/pybind_interface/basic/pybind_main_basic.cpp index 55cb5484..51fdc608 100644 --- a/pybind_interface/basic/pybind_main_basic.cpp +++ b/pybind_interface/basic/pybind_main_basic.cpp @@ -16,6 +16,7 @@ #include "../../lib/formux.h" #include "../../lib/simulator_basic.h" +#include "../../lib/util_cpu.h" namespace qsim { template diff --git a/pybind_interface/cuda/pybind_main_cuda.cpp b/pybind_interface/cuda/pybind_main_cuda.cpp index 391cb976..88fa3a61 100644 --- a/pybind_interface/cuda/pybind_main_cuda.cpp +++ b/pybind_interface/cuda/pybind_main_cuda.cpp @@ -41,6 +41,9 @@ namespace qsim { StateSpace::Parameter ss_params; Simulator::Parameter sim_params; }; + + inline void SetFlushToZeroAndDenormalsAreZeros() {} + inline void ClearFlushToZeroAndDenormalsAreZeros() {} } #include "../pybind_main.cpp" diff --git a/pybind_interface/custatevec/pybind_main_custatevec.cpp b/pybind_interface/custatevec/pybind_main_custatevec.cpp index 109cedae..1e399633 100644 --- a/pybind_interface/custatevec/pybind_main_custatevec.cpp +++ b/pybind_interface/custatevec/pybind_main_custatevec.cpp @@ -53,6 +53,9 @@ struct Factory { custatevecHandle_t custatevec_handle; }; +inline void SetFlushToZeroAndDenormalsAreZeros() {} +inline void ClearFlushToZeroAndDenormalsAreZeros() {} + } #include "../pybind_main.cpp" diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 428372d1..d02c0693 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -30,7 +30,6 @@ #include "../lib/qtrajectory.h" #include "../lib/run_qsim.h" #include "../lib/run_qsimh.h" -#include "../lib/util.h" using namespace qsim; diff --git a/pybind_interface/sse/pybind_main_sse.cpp b/pybind_interface/sse/pybind_main_sse.cpp index 4c72a65a..674aecad 100644 --- a/pybind_interface/sse/pybind_main_sse.cpp +++ b/pybind_interface/sse/pybind_main_sse.cpp @@ -16,6 +16,7 @@ #include "../../lib/formux.h" #include "../../lib/simulator_sse.h" +#include "../../lib/util_cpu.h" namespace qsim { template diff --git a/tests/BUILD b/tests/BUILD index 47f90535..af59659e 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -265,7 +265,7 @@ cc_library( "//lib:gate_appl", "//lib:gates_qsim", "//lib:io", - "//lib:util", + "//lib:util_cpu", ], testonly = 1, ) diff --git a/tests/simulator_testfixture.h b/tests/simulator_testfixture.h index 05cad5a6..ef335565 100644 --- a/tests/simulator_testfixture.h +++ b/tests/simulator_testfixture.h @@ -27,7 +27,7 @@ #include "../lib/gate_appl.h" #include "../lib/gates_qsim.h" #include "../lib/io.h" -#include "../lib/util.h" +#include "../lib/util_cpu.h" namespace qsim { From eb71c308aaba20d685a7612c3b4722fc6f241ab3 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Thu, 13 Jan 2022 14:19:37 +0100 Subject: [PATCH 191/246] Fix expectation values. --- lib/expect.h | 21 +++++++++++++++------ lib/qtrajectory.h | 2 +- pybind_interface/pybind_main.cpp | 8 +++++--- tests/expect_test.cc | 10 +++++++--- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/lib/expect.h b/lib/expect.h index 66ff817d..38fabfe4 100644 --- a/lib/expect.h +++ b/lib/expect.h @@ -42,18 +42,27 @@ struct OpString { * @param ket Temporary state vector. * @return The computed expectation value. */ -template +template std::complex ExpectationValue( const typename Fuser::Parameter& param, const std::vector>& strings, - const typename Simulator::StateSpace& ss, const Simulator& simulator, - const typename Simulator::State& state, typename Simulator::State& ket) { + const typename Simulator::StateSpace& state_space, + const Simulator& simulator, const typename Simulator::State& state, + typename Simulator::State& ket) { std::complex eval = 0; + if (state_space.IsNull(ket) || ket.num_qubits() < state.num_qubits()) { + ket = state_space.Create(state.num_qubits()); + if (state_space.IsNull(ket)) { + IO::errorf("not enough memory: is the number of qubits too large?\n"); + return eval; + } + } + for (const auto& str : strings) { if (str.ops.size() == 0) continue; - ss.Copy(state, ket); + state_space.Copy(state, ket); if (str.ops.size() == 1) { const auto& op = str.ops[0]; @@ -70,7 +79,7 @@ std::complex ExpectationValue( } } - eval += str.weight * ss.InnerProduct(state, ket); + eval += str.weight * state_space.InnerProduct(state, ket); } return eval; @@ -88,7 +97,7 @@ std::complex ExpectationValue( * @param state The state of the system. * @return The computed expectation value. */ -template +template std::complex ExpectationValue( const std::vector>& strings, const Simulator& simulator, const typename Simulator::State& state) { diff --git a/lib/qtrajectory.h b/lib/qtrajectory.h index bba4708c..773238c6 100644 --- a/lib/qtrajectory.h +++ b/lib/qtrajectory.h @@ -279,7 +279,7 @@ class QuantumTrajectorySimulator { NormalizeState(!unitary, state_space, unitary, state); - if (state_space.IsNull(scratch)) { + if (state_space.IsNull(scratch) || scratch.num_qubits() < num_qubits) { scratch = CreateState(num_qubits, state_space); if (state_space.IsNull(scratch)) { return false; diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index d02c0693..afdfbd16 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -692,9 +692,11 @@ class SimulatorHelper { if (opsum_qubits <= 6) { results.push_back(ExpectationValue(opsum, simulator, state)); } else { - Fuser::Parameter param; - results.push_back(ExpectationValue( - param, opsum, state_space, simulator, state, scratch)); + Fuser::Parameter params; + params.max_fused_size = max_fused_size; + params.verbosity = verbosity; + results.push_back(ExpectationValue( + params, opsum, state_space, simulator, state, scratch)); } } return results; diff --git a/tests/expect_test.cc b/tests/expect_test.cc index 19f4eb10..61e0fa77 100644 --- a/tests/expect_test.cc +++ b/tests/expect_test.cc @@ -94,7 +94,7 @@ TEST(ExpectTest, ExpectationValue) { Fuser::Parameter param; param.max_fused_size = 4; - State tmp_state = state_space.Create(num_qubits); + State tmp_state = state_space.Null(); for (unsigned k = 1; k <= 6; ++k) { std::vector>> strings; @@ -120,8 +120,12 @@ TEST(ExpectTest, ExpectationValue) { } } - auto evala = ExpectationValue(param, strings, state_space, simulator, - state, tmp_state); + if (k == 2) { + tmp_state = state_space.Create(num_qubits - 2); + } + + auto evala = ExpectationValue(param, strings, state_space, + simulator, state, tmp_state); EXPECT_NEAR(std::real(evala), expected_real[k - 1], 1e-6); EXPECT_NEAR(std::imag(evala), 0, 1e-8); From 19c6cd8a6dc2f449f53637afcf204cd6d59a005a Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 14 Jan 2022 14:36:53 -0800 Subject: [PATCH 192/246] Intermediate expectation values. --- pybind_interface/pybind_main.cpp | 140 ++++++++++++++++++++++++++ pybind_interface/pybind_main.h | 124 +++++++++++++++++++++++ qsimcirq/qsim_simulator.py | 168 ++++++++++++++++++++++++++++++- qsimcirq_tests/qsimcirq_test.py | 54 ++++++++++ 4 files changed, 484 insertions(+), 2 deletions(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index afdfbd16..7a63cc28 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -559,6 +559,71 @@ class SimulatorHelper { return results; } + template + static std::vector>> + simulate_moment_expectation_values( + const py::dict &options, + const std::vector>>, unsigned> + >>>& opsums_and_qubit_counts, + bool is_noisy, const StateType& input_state) { + auto helper = SimulatorHelper(options, is_noisy); + if (!helper.is_valid) { + return {}; + } + std::vector>> results( + opsums_and_qubit_counts.size() + ); + if (!is_noisy) { + // Init outside of simulation to enable stepping. + helper.init_state(input_state); + uint64_t begin = 0; + for (unsigned i = 0; i < opsums_and_qubit_counts.size(); ++i) { + auto& pair = opsums_and_qubit_counts[i]; + uint64_t end = std::get<0>(pair); + auto& counts = std::get<1>(pair); + if (!helper.simulate_subcircuit(begin, end)) { + return {}; + } + results[i] = helper.get_expectation_value(counts); + begin = end; + } + return results; + } + + // Aggregate expectation values for noisy circuits. + // TODO: split over steps + for (unsigned i = 0; i < opsums_and_qubit_counts.size(); ++i) { + auto& counts = std::get<1>(opsums_and_qubit_counts[i]); + results[i] = std::vector>(counts.size(), 0); + } + for (unsigned rep = 0; rep < helper.noisy_reps; ++rep) { + // Init outside of simulation to enable stepping. + helper.init_state(input_state); + uint64_t begin = 0; + for (unsigned i = 0; i < opsums_and_qubit_counts.size(); ++i) { + auto& pair = opsums_and_qubit_counts[i]; + uint64_t end = std::get<0>(pair); + auto& counts = std::get<1>(pair); + if (!helper.simulate_subcircuit(begin, end)) { + return {}; + } + auto evs = helper.get_expectation_value(counts); + for (unsigned j = 0; j < evs.size(); ++j) { + results[i][j] += evs[j]; + } + begin = end; + } + } + double inverse_num_reps = 1.0 / helper.noisy_reps; + for (unsigned i = 0; i < results.size(); ++i) { + for (unsigned j = 0; j < results[i].size(); ++j) { + results[i][j] *= inverse_num_reps; + } + } + return results; + } + private: SimulatorHelper(const py::dict &options, bool noisy) : factory(Factory(1, 1, 1)), @@ -658,6 +723,37 @@ class SimulatorHelper { return result; } + bool simulate_subcircuit(uint64_t begin, uint64_t end) { + bool result = false; + + if (is_noisy) { + std::vector stat; + auto params = get_noisy_params(); + NoisyCircuit subncircuit; + subncircuit.num_qubits = ncircuit.num_qubits; + subncircuit.channels = std::vector>( + ncircuit.channels.begin() + begin, + ncircuit.channels.begin() + end + ); + + Simulator simulator = factory.CreateSimulator(); + StateSpace state_space = factory.CreateStateSpace(); + + result = NoisyRunner::RunOnce(params, subncircuit, seed, state_space, + simulator, scratch, state, stat); + } else { + Circuit subcircuit; + subcircuit.num_qubits = circuit.num_qubits; + subcircuit.gates = std::vector( + circuit.gates.begin() + begin, + circuit.gates.begin() + end + ); + result = Runner::Run(get_params(), factory, subcircuit, state); + } + seed += 1; + return result; + } + py::array_t release_state_to_python() { StateSpace state_space = factory.CreateStateSpace(); state_space.InternalToNormalOrder(state); @@ -770,6 +866,28 @@ std::vector> qsim_simulate_expectation_values( options, opsums_and_qubit_counts, false, input_vector); } +std::vector>> +qsim_simulate_moment_expectation_values( + const py::dict &options, + const std::vector>>, unsigned> + >>>& opsums_and_qubit_counts, + uint64_t input_state) { + return SimulatorHelper::simulate_moment_expectation_values( + options, opsums_and_qubit_counts, false, input_state); +} + +std::vector>> +qsim_simulate_moment_expectation_values( + const py::dict &options, + const std::vector>>, unsigned> + >>>& opsums_and_qubit_counts, + const py::array_t &input_vector) { + return SimulatorHelper::simulate_moment_expectation_values( + options, opsums_and_qubit_counts, false, input_vector); +} + std::vector> qtrajectory_simulate_expectation_values( const py::dict &options, const std::vector> qtrajectory_simulate_expectation_values( options, opsums_and_qubit_counts, true, input_vector); } +std::vector>> +qtrajectory_simulate_moment_expectation_values( + const py::dict &options, + const std::vector>>, unsigned> + >>>& opsums_and_qubit_counts, + uint64_t input_state) { + return SimulatorHelper::simulate_moment_expectation_values( + options, opsums_and_qubit_counts, true, input_state); +} + +std::vector>> +qtrajectory_simulate_moment_expectation_values( + const py::dict &options, + const std::vector>>, unsigned> + >>>& opsums_and_qubit_counts, + const py::array_t &input_vector) { + return SimulatorHelper::simulate_moment_expectation_values( + options, opsums_and_qubit_counts, true, input_vector); +} + // Methods for sampling. std::vector qsim_sample(const py::dict &options) { diff --git a/pybind_interface/pybind_main.h b/pybind_interface/pybind_main.h index 90bcab6e..0b433a8a 100644 --- a/pybind_interface/pybind_main.h +++ b/pybind_interface/pybind_main.h @@ -116,6 +116,24 @@ std::vector> qsim_simulate_expectation_values( qsim::Cirq::GateCirq>>, unsigned>>& opsums_and_qubit_counts, const py::array_t &input_vector); +std::vector>> +qsim_simulate_moment_expectation_values( + const py::dict &options, + const std::vector>>, + unsigned + >>>>& opsums_and_qubit_counts, + uint64_t input_state); +std::vector>> +qsim_simulate_moment_expectation_values( + const py::dict &options, + const std::vector>>, + unsigned + >>>>& opsums_and_qubit_counts, + const py::array_t &input_vector); std::vector> qtrajectory_simulate_expectation_values( const py::dict &options, const std::vector> qtrajectory_simulate_expectation_values( qsim::Cirq::GateCirq>>, unsigned>>& opsums_and_qubit_counts, const py::array_t &input_vector); +std::vector>> +qtrajectory_simulate_moment_expectation_values( + const py::dict &options, + const std::vector>>, + unsigned + >>>>& opsums_and_qubit_counts, + uint64_t input_state); +std::vector>> +qtrajectory_simulate_moment_expectation_values( + const py::dict &options, + const std::vector>>, + unsigned + >>>>& opsums_and_qubit_counts, + const py::array_t &input_vector); // Hybrid simulator. std::vector> qsimh_simulate(const py::dict &options); @@ -186,6 +222,25 @@ std::vector> qsimh_simulate(const py::dict &options); &qsim_simulate_expectation_values), \ "Call the qsim simulator for expectation value simulation"); \ \ + m.def("qsim_simulate_moment_expectation_values", \ + static_cast>>(*)( \ + const py::dict&, \ + const std::vector, unsigned> \ + >>>&, \ + uint64_t)>( \ + &qsim_simulate_moment_expectation_values), \ + "Call the qsim simulator for step-by-step expectation value simulation"); \ + m.def("qsim_simulate_moment_expectation_values", \ + static_cast>>(*)( \ + const py::dict&, \ + const std::vector, unsigned> \ + >>>&, \ + const py::array_t&)>( \ + &qsim_simulate_moment_expectation_values), \ + "Call the qsim simulator for step-by-step expectation value simulation"); \ + \ m.def("qtrajectory_simulate_expectation_values", \ static_cast>(*)( \ const py::dict&, \ @@ -201,9 +256,31 @@ std::vector> qsimh_simulate(const py::dict &options); &qtrajectory_simulate_expectation_values), \ "Call the qtrajectory simulator for expectation value simulation"); \ \ + m.def("qtrajectory_simulate_moment_expectation_values", \ + static_cast>>(*)( \ + const py::dict&, \ + const std::vector, unsigned> \ + >>>&, \ + uint64_t)>( \ + &qtrajectory_simulate_moment_expectation_values), \ + "Call the qtrajectory simulator for step-by-step " \ + "expectation value simulation"); \ + m.def("qtrajectory_simulate_moment_expectation_values", \ + static_cast>>(*)( \ + const py::dict&, \ + const std::vector, unsigned> \ + >>>&, \ + const py::array_t&)>( \ + &qtrajectory_simulate_moment_expectation_values), \ + "Call the qtrajectory simulator for step-by-step " \ + "expectation value simulation"); \ + \ /* Method for hybrid simulation */ \ m.def("qsimh_simulate", &qsimh_simulate, "Call the qsimh simulator"); \ \ + using KrausOperator = qsim::KrausOperator; \ using GateKind = qsim::Cirq::GateKind; \ using Circuit = qsim::Circuit; \ using NoisyCircuit = qsim::NoisyCircuit; \ @@ -223,6 +300,12 @@ std::vector> qsimh_simulate(const py::dict &options); .def_readwrite("weight", &OpString::weight) \ .def_readwrite("ops", &OpString::ops); \ \ + py::class_(m, "KrausOperator") \ + .def(py::init<>()); \ + \ + py::class_(m, "GateCirq") \ + .def(py::init<>()); \ + \ py::enum_(m, "GateKind") \ .value("kI1", GateKind::kI1) \ .value("kI2", GateKind::kI2) \ @@ -346,6 +429,26 @@ std::vector> qsimh_simulate(const py::dict &options); &qsim_simulate_expectation_values), \ "Call the qsim simulator for expectation value simulation"); \ \ + m.def("qsim_simulate_moment_expectation_values", \ + static_cast>>(*)( \ + const py::dict&, \ + const std::vector, unsigned> \ + >>>&, \ + uint64_t)>( \ + &qsim_simulate_moment_expectation_values), \ + "Call the qsim simulator for step-by-step expectation value simulation"); \ + m.def("qsim_simulate_moment_expectation_values", \ + static_cast>>(*)( \ + const py::dict&, \ + const std::vector, unsigned> \ + >>>&, \ + const py::array_t&)>( \ + &qsim_simulate_moment_expectation_values), \ + "Call the qsim simulator for step-by-step expectation value simulation"); \ + \ + \ m.def("qtrajectory_simulate_expectation_values", \ static_cast>(*)( \ const py::dict&, \ @@ -361,6 +464,27 @@ std::vector> qsimh_simulate(const py::dict &options); &qtrajectory_simulate_expectation_values), \ "Call the qtrajectory simulator for expectation value simulation"); \ \ + m.def("qtrajectory_simulate_moment_expectation_values", \ + static_cast>>(*)( \ + const py::dict&, \ + const std::vector, unsigned> \ + >>>&, \ + uint64_t)>( \ + &qtrajectory_simulate_moment_expectation_values), \ + "Call the qtrajectory simulator for step-by-step " \ + "expectation value simulation"); \ + m.def("qtrajectory_simulate_moment_expectation_values", \ + static_cast>>(*)( \ + const py::dict&, \ + const std::vector, unsigned> \ + >>>&, \ + const py::array_t&)>( \ + &qtrajectory_simulate_moment_expectation_values), \ + "Call the qtrajectory simulator for step-by-step " \ + "expectation value simulation"); \ + \ /* Method for hybrid simulation */ \ m.def("qsimh_simulate", &qsimh_simulate, "Call the qsimh simulator"); #endif diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index b28078c9..c7460983 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -14,7 +14,8 @@ from collections import deque from dataclasses import dataclass -from typing import Any, Dict, List, Optional, Sequence, Tuple, Union +from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple, Union +from xml.etree.ElementPath import ops import cirq @@ -595,7 +596,7 @@ def simulate_expectation_values_sweep( Args: program: The circuit to simulate. observables: An observable or list of observables. - param_resolver: Parameters to run with the program. + params: Parameters to run with the program. qubit_order: Determines the canonical ordering of the qubits. This is often used in specifying the initial state, i.e. the ordering of the computational basis states. @@ -698,6 +699,144 @@ def simulate_expectation_values_sweep( return results + def simulate_moment_expectation_values( + self, + program: cirq.Circuit, + indexed_observables: Union[ + Dict[int, Union[cirq.PauliSumLike, List[cirq.PauliSumLike]]], + cirq.PauliSumLike, + List[cirq.PauliSumLike], + ], + param_resolver: cirq.ParamResolver, + qubit_order: cirq.QubitOrderOrList = cirq.QubitOrder.DEFAULT, + initial_state: Any = None, + ) -> List[List[float]]: + """Calculates expectation values at each moment of a circuit. + + Args: + program: The circuit to simulate. + indexed_observables: A map of moment indices to an observable + or list of observables to calculate after that moment. As a + convenience, users can instead pass in a single observable + or observable list to calculate after ALL moments. + param_resolver: Parameters to run with the program. + qubit_order: Determines the canonical ordering of the qubits. This + is often used in specifying the initial state, i.e. the + ordering of the computational basis states. + initial_state: The initial state for the simulation. The form of + this state depends on the simulation implementation. See + documentation of the implementing class for details. + permit_terminal_measurements: If the provided circuit ends with + measurement(s), this method will generate an error unless this + is set to True. This is meant to prevent measurements from + ruining expectation value calculations. + + Returns: + A list of expectation values for each moment m in the circuit, + where value `n` corresponds to `indexed_observables[m][n]`. + + Raises: + ValueError if 'program' has terminal measurement(s) and + 'permit_terminal_measurements' is False. (Note: We cannot test this + until Cirq's `are_any_measurements_terminal` is released.) + """ + if not isinstance(indexed_observables, Dict): + if not isinstance(indexed_observables, List): + indexed_observables = [ + (i, [indexed_observables]) for i, _ in enumerate(program) + ] + else: + indexed_observables = [ + (i, indexed_observables) for i, _ in enumerate(program) + ] + else: + indexed_observables = [ + (i, obs) if isinstance(obs, List) else (i, [obs]) + for i, obs in indexed_observables.items() + ] + indexed_observables.sort(key=lambda x: x[0]) + psum_pairs = [ + (i, [cirq.PauliSum.wrap(pslike) for pslike in obs_list]) + for i, obs_list in indexed_observables + ] + + all_qubits = program.all_qubits() + cirq_order = cirq.QubitOrder.as_qubit_order(qubit_order).order_for(all_qubits) + qsim_order = list(reversed(cirq_order)) + num_qubits = len(qsim_order) + qubit_map = {qubit: index for index, qubit in enumerate(qsim_order)} + + opsums_and_qcount_map = {} + for i, psumlist in psum_pairs: + opsums_and_qcount_map[i] = [] + for psum in psumlist: + opsum = [] + opsum_qubits = set() + for pstr in psum: + opstring = qsim.OpString() + opstring.weight = pstr.coefficient + for q, pauli in pstr.items(): + op = pauli.on(q) + opsum_qubits.add(q) + qsimc.add_op_to_opstring(op, qubit_map, opstring) + opsum.append(opstring) + opsums_and_qcount_map[i].append((opsum, len(opsum_qubits))) + + if initial_state is None: + initial_state = 0 + if not isinstance(initial_state, (int, np.ndarray)): + raise TypeError("initial_state must be an int or state vector.") + + # Add noise to the circuit if a noise model was provided. + program = qsimc.QSimCircuit( + self.noise.noisy_moments(program, sorted(all_qubits)) + if self.noise is not cirq.NO_NOISE + else program, + device=program.device, + ) + + options = {} + options.update(self.qsim_options) + + param_resolver = cirq.to_resolvers(param_resolver) + if isinstance(initial_state, np.ndarray): + if initial_state.dtype != np.complex64: + raise TypeError(f"initial_state vector must have dtype np.complex64.") + input_vector = initial_state.view(np.float32) + if len(input_vector) != 2 ** num_qubits * 2: + raise ValueError( + f"initial_state vector size must match number of qubits." + f"Expected: {2**num_qubits * 2} Received: {len(input_vector)}" + ) + + is_noisy = _needs_trajectories(program) + if is_noisy: + translator_fn_name = "translate_cirq_to_qtrajectory" + ev_simulator_fn = ( + self._sim_module.qtrajectory_simulate_moment_expectation_values + ) + else: + translator_fn_name = "translate_cirq_to_qsim" + ev_simulator_fn = self._sim_module.qsim_simulate_moment_expectation_values + + solved_circuit = cirq.resolve_parameters(program, param_resolver) + options["c"], opsum_reindex = self._translate_moments( + solved_circuit, + translator_fn_name, + cirq_order, + is_noisy, + ) + opsums_and_qubit_counts = [] + for m, opsum_qc in opsums_and_qcount_map.items(): + pair = (opsum_reindex[m], opsum_qc) + opsums_and_qubit_counts.append(pair) + options["s"] = self.get_seed() + + if isinstance(initial_state, int): + return ev_simulator_fn(options, opsums_and_qubit_counts, initial_state) + elif isinstance(initial_state, np.ndarray): + return ev_simulator_fn(options, opsums_and_qubit_counts, input_vector) + def _translate_circuit( self, circuit: Any, @@ -718,3 +857,28 @@ def _translate_circuit( self._translated_circuits.append((circuit, translated_circuit)) return translated_circuit + + def _translate_moments( + self, + circuit: Any, + translator_fn_name: str, + qubit_order: cirq.QubitOrderOrList, + is_noisy: bool, + ): + """Translates a circuit by moments. + + Returns: + A tuple containing the translated circuit and the gate indices + corresponding to moment boundaries. + """ + reindex = [0] + for moment in circuit: + subc = self._translate_circuit( + qsimc.QSimCircuit(cirq.Circuit(moment)), + translator_fn_name, + qubit_order, + ) + reindex.append(len(subc.channels if is_noisy else subc.gates) + reindex[-1]) + + full_circuit = self._translate_circuit(circuit, translator_fn_name, qubit_order) + return full_circuit, reindex[1:] diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 2ccccd7d..f5c0cea8 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -471,6 +471,60 @@ def test_expectation_values(mode: str): assert cirq.approx_eq(qsim_result, cirq_result, atol=1e-6) +@pytest.mark.parametrize("mode", ["noiseless", "noisy"]) +def test_moment_expectation_values(mode: str): + # Perform a single-pass Rabi oscillation, measuring Z at each step. + q0 = cirq.LineQubit(0) + steps = 20 + circuit = cirq.Circuit(*[cirq.X(q0) ** 0.05 for _ in range(steps)]) + psum = cirq.Z(q0) + params = {} + + if mode == "noisy": + circuit.append(NoiseTrigger().on(q0)) + + qsim_simulator = qsimcirq.QSimSimulator() + qsim_result = qsim_simulator.simulate_moment_expectation_values( + circuit, psum, params + ) + # Omit noise trigger element + results = [r[0] for r in qsim_result][:20] + assert np.allclose( + [result.real for result in results], + [np.cos(np.pi * (i + 1) / 20) for i in range(steps)], + atol=1e-6, + ) + + +@pytest.mark.parametrize("mode", ["noiseless", "noisy"]) +def test_select_moment_expectation_values(mode: str): + # Measure different observables after specified steps. + q0, q1 = cirq.LineQubit.range(2) + circuit = cirq.Circuit( + cirq.Moment(cirq.X(q0), cirq.H(q1)), + cirq.Moment(cirq.H(q0), cirq.Z(q1)), + cirq.Moment(cirq.Z(q0), cirq.H(q1)), + cirq.Moment(cirq.H(q0), cirq.X(q1)), + ) + psum_map = { + 0: cirq.Z(q0), + 1: [cirq.X(q0), cirq.Z(q1)], + 3: [cirq.Z(q0), cirq.Z(q1)], + } + params = {} + + if mode == "noisy": + circuit.append(NoiseTrigger().on(q0)) + + qsim_simulator = qsimcirq.QSimSimulator() + qsim_result = qsim_simulator.simulate_moment_expectation_values( + circuit, psum_map, params + ) + expected_results = [[-1], [-1, 0], [1, 1]] + for i, result in enumerate(qsim_result): + assert np.allclose(result, expected_results[i]) + + def test_expectation_values_terminal_measurement_check(): a, b = [ cirq.GridQubit(0, 0), From dc62bf485f6f6c444c702ab1211f393a7f0d1262 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 18 Jan 2022 09:18:21 -0800 Subject: [PATCH 193/246] Use existing subcircuit method for noisy sim --- pybind_interface/pybind_main.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 7a63cc28..ec842cfd 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -729,18 +729,15 @@ class SimulatorHelper { if (is_noisy) { std::vector stat; auto params = get_noisy_params(); - NoisyCircuit subncircuit; - subncircuit.num_qubits = ncircuit.num_qubits; - subncircuit.channels = std::vector>( - ncircuit.channels.begin() + begin, - ncircuit.channels.begin() + end - ); - Simulator simulator = factory.CreateSimulator(); StateSpace state_space = factory.CreateStateSpace(); - result = NoisyRunner::RunOnce(params, subncircuit, seed, state_space, - simulator, scratch, state, stat); + result = NoisyRunner::RunOnce( + params, ncircuit.num_qubits, + ncircuit.channels.begin() + begin, + ncircuit.channels.begin() + end, + seed, state_space, simulator, scratch, state, stat + ); } else { Circuit subcircuit; subcircuit.num_qubits = circuit.num_qubits; From 3a074162b307a863132113522e07c07a6b9c605a Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 18 Jan 2022 12:09:00 -0800 Subject: [PATCH 194/246] Only translate circuit once. --- pybind_interface/pybind_main.cpp | 46 ++++++++++++++++++++++++++++++++ pybind_interface/pybind_main.h | 11 ++++++++ qsimcirq/qsim_simulator.py | 7 ++++- qsimcirq_tests/qsimcirq_test.py | 2 +- 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index ec842cfd..7c95067d 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -273,6 +273,25 @@ void control_last_gate(const std::vector& qubits, MakeControlledGate(qubits, values, circuit->gates.back()); } +void compose_circuits(Circuit>* target, + Circuit>* suffix) { + // Consumes 'suffix' and composes it onto the end of 'target'. + // After calling this method, 'suffix' should be discarded. + unsigned target_time = 0; + for (const auto& gate : target->gates) { + if (gate.time >= target_time) { + // Target duration is the max gate time, plus one. + target_time = gate.time + 1; + } + } + for (auto& gate : suffix->gates) { + gate.time += target_time; + } + target->gates.insert(target->gates.end(), + std::make_move_iterator(suffix->gates.begin()), + std::make_move_iterator(suffix->gates.end())); +} + template Channel create_single_gate_channel(Gate gate) { auto gate_kind = KrausOperator::kNormal; @@ -342,6 +361,33 @@ void add_channel(const unsigned time, ncircuit->channels.push_back(channel); } +void compose_noisy_circuits(NoisyCircuit>* target, + NoisyCircuit>* suffix) { + // Consumes 'suffix' and composes it onto the end of 'target'. + // After calling this method, 'suffix' should be discarded. + unsigned target_time = 0; + for (const auto& channel : target->channels) { + for (const auto& kraus_op : channel) { + for (const auto& op : kraus_op.ops) { + if (op.time >= target_time) { + // Target duration is the max gate time, plus one. + target_time = op.time + 1; + } + } + } + } + for (auto& channel : suffix->channels) { + for (auto& kraus_op : channel) { + for (auto& op : kraus_op.ops) { + op.time += target_time; + } + } + } + target->channels.insert(target->channels.end(), + std::make_move_iterator(suffix->channels.begin()), + std::make_move_iterator(suffix->channels.end())); +} + void add_gate_to_opstring(const Cirq::GateKind gate_kind, const std::vector& qubits, OpString>* opstring) { diff --git a/pybind_interface/pybind_main.h b/pybind_interface/pybind_main.h index 0b433a8a..3349b363 100644 --- a/pybind_interface/pybind_main.h +++ b/pybind_interface/pybind_main.h @@ -47,6 +47,9 @@ void control_last_gate(const std::vector& qubits, const std::vector& values, qsim::Circuit>* circuit); +void compose_circuits(qsim::Circuit>* target, + qsim::Circuit>* suffix); + // Methods for mutating noisy circuits. void add_gate_channel( const qsim::Cirq::GateKind gate_kind, @@ -75,6 +78,10 @@ void add_channel(const unsigned time, prob_matrix_unitary_triples, qsim::NoisyCircuit>* ncircuit); +void compose_noisy_circuits( + qsim::NoisyCircuit>* target, + qsim::NoisyCircuit>* suffix); + // Method for populating opstrings. void add_gate_to_opstring( const qsim::Cirq::GateKind gate_kind, @@ -361,6 +368,8 @@ std::vector> qsimh_simulate(const py::dict &options); "Adds a matrix-defined gate to the given circuit."); \ m.def("control_last_gate", &control_last_gate, \ "Applies controls to the final gate of a circuit."); \ + m.def("compose_circuits", &compose_circuits, \ + "Composes target and suffix circuits into a single circuit."); \ \ m.def("add_gate_channel", &add_gate_channel, \ "Adds a gate to the given noisy circuit."); \ @@ -373,6 +382,8 @@ std::vector> qsimh_simulate(const py::dict &options); \ m.def("add_channel", &add_channel, \ "Adds a channel to the given noisy circuit."); \ + m.def("compose_noisy_circuits", &compose_noisy_circuits, \ + "Composes target and suffix noisy circuits into a single noisy circuit.");\ \ m.def("add_gate_to_opstring", &add_gate_to_opstring, \ "Adds a gate to the given opstring."); diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index c7460983..9ac8cb6e 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -872,6 +872,8 @@ def _translate_moments( corresponding to moment boundaries. """ reindex = [0] + full_circuit = qsim.NoisyCircuit() if is_noisy else qsim.Circuit() + full_circuit.num_qubits = len(circuit.all_qubits()) for moment in circuit: subc = self._translate_circuit( qsimc.QSimCircuit(cirq.Circuit(moment)), @@ -879,6 +881,9 @@ def _translate_moments( qubit_order, ) reindex.append(len(subc.channels if is_noisy else subc.gates) + reindex[-1]) + if is_noisy: + qsim.compose_noisy_circuits(full_circuit, subc) + else: + qsim.compose_circuits(full_circuit, subc) - full_circuit = self._translate_circuit(circuit, translator_fn_name, qubit_order) return full_circuit, reindex[1:] diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index f5c0cea8..ac855a87 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -488,7 +488,7 @@ def test_moment_expectation_values(mode: str): circuit, psum, params ) # Omit noise trigger element - results = [r[0] for r in qsim_result][:20] + results = [r[0] for r in qsim_result][:steps] assert np.allclose( [result.real for result in results], [np.cos(np.pi * (i + 1) / 20) for i in range(steps)], From 48125821591695522230d3a14c7af359556e5f36 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 19 Jan 2022 13:28:47 -0800 Subject: [PATCH 195/246] Handle gate-counting in translate_circuit. --- pybind_interface/pybind_main.cpp | 1 - qsimcirq/qsim_circuit.py | 14 +++++--- qsimcirq/qsim_simulator.py | 58 ++++++++++---------------------- qsimcirq/qsimh_simulator.py | 2 +- 4 files changed, 28 insertions(+), 47 deletions(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 7c95067d..e58f3d98 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -638,7 +638,6 @@ class SimulatorHelper { } // Aggregate expectation values for noisy circuits. - // TODO: split over steps for (unsigned i = 0; i < opsums_and_qubit_counts.size(); ++i) { auto& counts = std::get<1>(opsums_and_qubit_counts[i]); results[i] = std::vector>(counts.size(), 0); diff --git a/qsimcirq/qsim_circuit.py b/qsimcirq/qsim_circuit.py index a4811e2b..053c9e6f 100644 --- a/qsimcirq/qsim_circuit.py +++ b/qsimcirq/qsim_circuit.py @@ -285,7 +285,8 @@ def translate_cirq_to_qsim( """ Translates this Cirq circuit to the qsim representation. :qubit_order: Ordering of qubits - :return: a C++ qsim Circuit object + :return: a tuple of (C++ qsim Circuit object, moment boundary + gate indices) """ qsim_circuit = qsim.Circuit() @@ -309,6 +310,7 @@ def to_matrix(op: cirq.ops.GateOperation): qubit_to_index_dict = {q: i for i, q in enumerate(ordered_qubits)} time_offset = 0 + moment_indices = [] for moment in self: ops_by_gate = [ cirq.decompose(op, fallback_decomposer=to_matrix, keep=has_qsim_kind) @@ -326,8 +328,9 @@ def to_matrix(op: cirq.ops.GateOperation): gate_kind = _cirq_gate_kind(qsim_op.gate) add_op_to_circuit(qsim_op, time, qubit_to_index_dict, qsim_circuit) time_offset += moment_length + moment_indices.append(len(qsim_circuit.gates)) - return qsim_circuit + return qsim_circuit, moment_indices def translate_cirq_to_qtrajectory( self, qubit_order: cirq.ops.QubitOrderOrList = cirq.ops.QubitOrder.DEFAULT @@ -335,7 +338,8 @@ def translate_cirq_to_qtrajectory( """ Translates this noisy Cirq circuit to the qsim representation. :qubit_order: Ordering of qubits - :return: a C++ qsim NoisyCircuit object + :return: a tuple of (C++ qsim NoisyCircuit object, moment boundary + gate indices) """ qsim_ncircuit = qsim.NoisyCircuit() ordered_qubits = cirq.ops.QubitOrder.as_qubit_order(qubit_order).order_for( @@ -359,6 +363,7 @@ def to_matrix(op: cirq.ops.GateOperation): qubit_to_index_dict = {q: i for i, q in enumerate(ordered_qubits)} time_offset = 0 + moment_indices = [] for moment in self: moment_length = 0 ops_by_gate = [] @@ -419,5 +424,6 @@ def to_matrix(op: cirq.ops.GateOperation): qubits = [qubit_to_index_dict[q] for q in channel.qubits] qsim.add_channel(time_offset, qubits, chdata, qsim_ncircuit) time_offset += moment_length + moment_indices.append(len(qsim_ncircuit.channels)) - return qsim_ncircuit + return qsim_ncircuit, moment_indices diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 9ac8cb6e..c52b8474 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -241,7 +241,11 @@ def __init__( else: self._sim_module = qsim - # Deque of (, ) tuples. + # Deque of ( + # , + # , + # + # ) tuples. self._translated_circuits = deque(maxlen=circuit_memoization_size) def get_seed(self): @@ -363,7 +367,7 @@ def _sample_measure_results( for op in program.moments[i] ) translator_fn_name = "translate_cirq_to_qsim" - options["c"] = self._translate_circuit( + options["c"], _ = self._translate_circuit( program, translator_fn_name, cirq.QubitOrder.DEFAULT, @@ -384,7 +388,7 @@ def _sample_measure_results( results[key] = full_results[:, meas_indices] ^ invert_mask else: - options["c"] = self._translate_circuit( + options["c"], _ = self._translate_circuit( program, translator_fn_name, cirq.QubitOrder.DEFAULT, @@ -463,7 +467,7 @@ def compute_amplitudes_sweep( for prs in param_resolvers: solved_circuit = cirq.resolve_parameters(program, prs) - options["c"] = self._translate_circuit( + options["c"], _ = self._translate_circuit( solved_circuit, translator_fn_name, cirq_order, @@ -553,7 +557,7 @@ def simulate_sweep( for prs in param_resolvers: solved_circuit = cirq.resolve_parameters(program, prs) - options["c"] = self._translate_circuit( + options["c"], _ = self._translate_circuit( solved_circuit, translator_fn_name, cirq_order, @@ -684,7 +688,7 @@ def simulate_expectation_values_sweep( for prs in param_resolvers: solved_circuit = cirq.resolve_parameters(program, prs) - options["c"] = self._translate_circuit( + options["c"], _ = self._translate_circuit( solved_circuit, translator_fn_name, cirq_order, @@ -820,11 +824,10 @@ def simulate_moment_expectation_values( ev_simulator_fn = self._sim_module.qsim_simulate_moment_expectation_values solved_circuit = cirq.resolve_parameters(program, param_resolver) - options["c"], opsum_reindex = self._translate_moments( + options["c"], opsum_reindex = self._translate_circuit( solved_circuit, translator_fn_name, cirq_order, - is_noisy, ) opsums_and_qubit_counts = [] for m, opsum_qc in opsums_and_qcount_map.items(): @@ -846,44 +849,17 @@ def _translate_circuit( # If the circuit is memoized, reuse the corresponding translated # circuit. translated_circuit = None - for original, translated in self._translated_circuits: + for original, translated, m_indices in self._translated_circuits: if original == circuit: translated_circuit = translated + moment_indices = m_indices break if translated_circuit is None: translator_fn = getattr(circuit, translator_fn_name) - translated_circuit = translator_fn(qubit_order) - self._translated_circuits.append((circuit, translated_circuit)) - - return translated_circuit - - def _translate_moments( - self, - circuit: Any, - translator_fn_name: str, - qubit_order: cirq.QubitOrderOrList, - is_noisy: bool, - ): - """Translates a circuit by moments. - - Returns: - A tuple containing the translated circuit and the gate indices - corresponding to moment boundaries. - """ - reindex = [0] - full_circuit = qsim.NoisyCircuit() if is_noisy else qsim.Circuit() - full_circuit.num_qubits = len(circuit.all_qubits()) - for moment in circuit: - subc = self._translate_circuit( - qsimc.QSimCircuit(cirq.Circuit(moment)), - translator_fn_name, - qubit_order, + translated_circuit, moment_indices = translator_fn(qubit_order) + self._translated_circuits.append( + (circuit, translated_circuit, moment_indices) ) - reindex.append(len(subc.channels if is_noisy else subc.gates) + reindex[-1]) - if is_noisy: - qsim.compose_noisy_circuits(full_circuit, subc) - else: - qsim.compose_circuits(full_circuit, subc) - return full_circuit, reindex[1:] + return translated_circuit, moment_indices diff --git a/qsimcirq/qsimh_simulator.py b/qsimcirq/qsimh_simulator.py index ccf1c1a0..6a060c42 100644 --- a/qsimcirq/qsimh_simulator.py +++ b/qsimcirq/qsimh_simulator.py @@ -65,7 +65,7 @@ def compute_amplitudes_sweep( solved_circuit = cirq.resolve_parameters(program, prs) - options["c"] = solved_circuit.translate_cirq_to_qsim(qubit_order) + options["c"], _ = solved_circuit.translate_cirq_to_qsim(qubit_order) options.update(self.qsimh_options) amplitudes = qsim.qsimh_simulate(options) From c82c5e6b4998fde65308a09e271ffa7a524fe91b Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 19 Jan 2022 13:38:14 -0800 Subject: [PATCH 196/246] Remove compose_circuit methods --- pybind_interface/pybind_main.cpp | 46 -------------------------------- pybind_interface/pybind_main.h | 11 -------- 2 files changed, 57 deletions(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index e58f3d98..015c29a6 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -273,25 +273,6 @@ void control_last_gate(const std::vector& qubits, MakeControlledGate(qubits, values, circuit->gates.back()); } -void compose_circuits(Circuit>* target, - Circuit>* suffix) { - // Consumes 'suffix' and composes it onto the end of 'target'. - // After calling this method, 'suffix' should be discarded. - unsigned target_time = 0; - for (const auto& gate : target->gates) { - if (gate.time >= target_time) { - // Target duration is the max gate time, plus one. - target_time = gate.time + 1; - } - } - for (auto& gate : suffix->gates) { - gate.time += target_time; - } - target->gates.insert(target->gates.end(), - std::make_move_iterator(suffix->gates.begin()), - std::make_move_iterator(suffix->gates.end())); -} - template Channel create_single_gate_channel(Gate gate) { auto gate_kind = KrausOperator::kNormal; @@ -361,33 +342,6 @@ void add_channel(const unsigned time, ncircuit->channels.push_back(channel); } -void compose_noisy_circuits(NoisyCircuit>* target, - NoisyCircuit>* suffix) { - // Consumes 'suffix' and composes it onto the end of 'target'. - // After calling this method, 'suffix' should be discarded. - unsigned target_time = 0; - for (const auto& channel : target->channels) { - for (const auto& kraus_op : channel) { - for (const auto& op : kraus_op.ops) { - if (op.time >= target_time) { - // Target duration is the max gate time, plus one. - target_time = op.time + 1; - } - } - } - } - for (auto& channel : suffix->channels) { - for (auto& kraus_op : channel) { - for (auto& op : kraus_op.ops) { - op.time += target_time; - } - } - } - target->channels.insert(target->channels.end(), - std::make_move_iterator(suffix->channels.begin()), - std::make_move_iterator(suffix->channels.end())); -} - void add_gate_to_opstring(const Cirq::GateKind gate_kind, const std::vector& qubits, OpString>* opstring) { diff --git a/pybind_interface/pybind_main.h b/pybind_interface/pybind_main.h index 3349b363..0b433a8a 100644 --- a/pybind_interface/pybind_main.h +++ b/pybind_interface/pybind_main.h @@ -47,9 +47,6 @@ void control_last_gate(const std::vector& qubits, const std::vector& values, qsim::Circuit>* circuit); -void compose_circuits(qsim::Circuit>* target, - qsim::Circuit>* suffix); - // Methods for mutating noisy circuits. void add_gate_channel( const qsim::Cirq::GateKind gate_kind, @@ -78,10 +75,6 @@ void add_channel(const unsigned time, prob_matrix_unitary_triples, qsim::NoisyCircuit>* ncircuit); -void compose_noisy_circuits( - qsim::NoisyCircuit>* target, - qsim::NoisyCircuit>* suffix); - // Method for populating opstrings. void add_gate_to_opstring( const qsim::Cirq::GateKind gate_kind, @@ -368,8 +361,6 @@ std::vector> qsimh_simulate(const py::dict &options); "Adds a matrix-defined gate to the given circuit."); \ m.def("control_last_gate", &control_last_gate, \ "Applies controls to the final gate of a circuit."); \ - m.def("compose_circuits", &compose_circuits, \ - "Composes target and suffix circuits into a single circuit."); \ \ m.def("add_gate_channel", &add_gate_channel, \ "Adds a gate to the given noisy circuit."); \ @@ -382,8 +373,6 @@ std::vector> qsimh_simulate(const py::dict &options); \ m.def("add_channel", &add_channel, \ "Adds a channel to the given noisy circuit."); \ - m.def("compose_noisy_circuits", &compose_noisy_circuits, \ - "Composes target and suffix noisy circuits into a single noisy circuit.");\ \ m.def("add_gate_to_opstring", &add_gate_to_opstring, \ "Adds a gate to the given opstring."); From bcf026ec61afdb160d301b705729f14759343b36 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 21 Jan 2022 12:54:11 -0800 Subject: [PATCH 197/246] Remove gates reference in Python --- pybind_interface/pybind_main.h | 7 ------- qsimcirq/qsim_circuit.py | 12 ++++++++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/pybind_interface/pybind_main.h b/pybind_interface/pybind_main.h index 0b433a8a..aed6b615 100644 --- a/pybind_interface/pybind_main.h +++ b/pybind_interface/pybind_main.h @@ -280,7 +280,6 @@ std::vector> qsimh_simulate(const py::dict &options); /* Method for hybrid simulation */ \ m.def("qsimh_simulate", &qsimh_simulate, "Call the qsimh simulator"); \ \ - using KrausOperator = qsim::KrausOperator; \ using GateKind = qsim::Cirq::GateKind; \ using Circuit = qsim::Circuit; \ using NoisyCircuit = qsim::NoisyCircuit; \ @@ -300,12 +299,6 @@ std::vector> qsimh_simulate(const py::dict &options); .def_readwrite("weight", &OpString::weight) \ .def_readwrite("ops", &OpString::ops); \ \ - py::class_(m, "KrausOperator") \ - .def(py::init<>()); \ - \ - py::class_(m, "GateCirq") \ - .def(py::init<>()); \ - \ py::enum_(m, "GateKind") \ .value("kI1", GateKind::kI1) \ .value("kI2", GateKind::kI2) \ diff --git a/qsimcirq/qsim_circuit.py b/qsimcirq/qsim_circuit.py index 053c9e6f..912f8fd7 100644 --- a/qsimcirq/qsim_circuit.py +++ b/qsimcirq/qsim_circuit.py @@ -310,6 +310,7 @@ def to_matrix(op: cirq.ops.GateOperation): qubit_to_index_dict = {q: i for i, q in enumerate(ordered_qubits)} time_offset = 0 + gate_count = 0 moment_indices = [] for moment in self: ops_by_gate = [ @@ -325,10 +326,10 @@ def to_matrix(op: cirq.ops.GateOperation): continue qsim_op = gate_ops[gi] time = time_offset + gi - gate_kind = _cirq_gate_kind(qsim_op.gate) add_op_to_circuit(qsim_op, time, qubit_to_index_dict, qsim_circuit) + gate_count += 1 time_offset += moment_length - moment_indices.append(len(qsim_circuit.gates)) + moment_indices.append(gate_count) return qsim_circuit, moment_indices @@ -363,6 +364,7 @@ def to_matrix(op: cirq.ops.GateOperation): qubit_to_index_dict = {q: i for i, q in enumerate(ordered_qubits)} time_offset = 0 + gate_count = 0 moment_indices = [] for moment in self: moment_length = 0 @@ -397,8 +399,8 @@ def to_matrix(op: cirq.ops.GateOperation): continue qsim_op = gate_ops[gi] time = time_offset + gi - gate_kind = _cirq_gate_kind(qsim_op.gate) add_op_to_circuit(qsim_op, time, qubit_to_index_dict, qsim_ncircuit) + gate_count += 1 # Handle mixture output. for mixture in ops_by_mix: mixdata = [] @@ -410,6 +412,7 @@ def to_matrix(op: cirq.ops.GateOperation): mixdata.append((prob, mat.view(np.float32), unitary)) qubits = [qubit_to_index_dict[q] for q in mixture.qubits] qsim.add_channel(time_offset, qubits, mixdata, qsim_ncircuit) + gate_count += 1 # Handle channel output. for channel in ops_by_channel: chdata = [] @@ -423,7 +426,8 @@ def to_matrix(op: cirq.ops.GateOperation): chdata.append((lower_bound_prob, mat.view(np.float32), unitary)) qubits = [qubit_to_index_dict[q] for q in channel.qubits] qsim.add_channel(time_offset, qubits, chdata, qsim_ncircuit) + gate_count += 1 time_offset += moment_length - moment_indices.append(len(qsim_ncircuit.channels)) + moment_indices.append(gate_count) return qsim_ncircuit, moment_indices From f1990876a484cfc24f1786bb59c063797158fa46 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 24 Jan 2022 08:22:21 -0800 Subject: [PATCH 198/246] Use resize --- pybind_interface/pybind_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 015c29a6..3cde361b 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -594,7 +594,7 @@ class SimulatorHelper { // Aggregate expectation values for noisy circuits. for (unsigned i = 0; i < opsums_and_qubit_counts.size(); ++i) { auto& counts = std::get<1>(opsums_and_qubit_counts[i]); - results[i] = std::vector>(counts.size(), 0); + results[i].resize(counts.size(), 0); } for (unsigned rep = 0; rep < helper.noisy_reps; ++rep) { // Init outside of simulation to enable stepping. From 52a197962497d5591693c7afb72a18676487098e Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Thu, 27 Jan 2022 17:31:26 +0100 Subject: [PATCH 199/246] Remove temporary vector from trajectories. --- apps/qsim_qtrajectory_cuda.cu | 4 +- docs/choose_hw.md | 8 +- lib/BUILD | 5 +- lib/channel.h | 68 ++++++++ lib/channels_cirq.h | 51 ++++-- lib/channels_qsim.h | 22 ++- lib/matrix.h | 33 ++++ lib/qtrajectory.h | 52 +++---- pybind_interface/pybind_main.cpp | 8 +- tests/BUILD | 18 +++ tests/channel_test.cc | 256 +++++++++++++++++++++++++++++++ tests/make.sh | 1 + tests/qtrajectory_testfixture.h | 125 +++++++++++++-- 13 files changed, 578 insertions(+), 73 deletions(-) create mode 100644 tests/channel_test.cc diff --git a/apps/qsim_qtrajectory_cuda.cu b/apps/qsim_qtrajectory_cuda.cu index ab9f122a..223add5e 100644 --- a/apps/qsim_qtrajectory_cuda.cu +++ b/apps/qsim_qtrajectory_cuda.cu @@ -242,7 +242,6 @@ int main(int argc, char* argv[]) { StateSpace state_space = factory.CreateStateSpace(); State state = state_space.Create(circuit.num_qubits); - State scratch = state_space.Null(); if (state_space.IsNull(state)) { IO::errorf("not enough memory: is the number of qubits too large?\n"); @@ -274,8 +273,7 @@ int main(int argc, char* argv[]) { for (unsigned s = 0; s < noisy_circuits.size(); ++s) { std::vector stat; if (!QTSimulator::RunOnce(param3, noisy_circuits[s], seed++, - state_space, simulator, scratch, state, - stat)) { + state_space, simulator, state, stat)) { return 1; } diff --git a/docs/choose_hw.md b/docs/choose_hw.md index 09dd9177..3be2bc06 100644 --- a/docs/choose_hw.md +++ b/docs/choose_hw.md @@ -38,10 +38,7 @@ below](#5_consider_multiple_compute_nodes). ### 2. Estimate your memory requirements You can estimate your memory requirements with the following rule of thumb: - -* Noiseless simulation: $ memory\ required = 8 \cdot 2^N bytes $ for an N-qubit - circuit -* Noisy simulation: $ memory\ required = 16 \cdot 2^N bytes $ for an N-qubit circuit +$ memory\ required = 8 \cdot 2^N bytes $ for an N-qubit circuit In addition to memory size, consider the bandwidth of your memory. qsim performs best when it can use the maximum number of threads. Multi-threaded simulation @@ -121,8 +118,7 @@ depth beyond 20 qubits. * How you can represent your noise model using Kraus operator formalism: * Performance is best in the case where all Kraus operators are proportional to unitary matrices, such as when using only a - depolarizing channel. In this case, memory requirements are equal to - noiseless memory requirements (8\*2^n bytes). + depolarizing channel. * Using noise which cannot be represented with Kraus operators proportional to unitary matrices, can slow down simulations by a factor of up to 6** **compared to using a depolarizing channel only diff --git a/lib/BUILD b/lib/BUILD index cc56dbd2..9be39dea 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -561,7 +561,10 @@ cc_library( cc_library( name = "channel", hdrs = ["channel.h"], - deps = [":gate"], + deps = [ + ":gate", + ":matrix", + ], ) cc_library( diff --git a/lib/channel.h b/lib/channel.h index 5f2a187b..372a174c 100644 --- a/lib/channel.h +++ b/lib/channel.h @@ -15,7 +15,11 @@ #ifndef CHANNEL_H_ #define CHANNEL_H_ +#include +#include + #include "gate.h" +#include "matrix.h" namespace qsim { @@ -24,6 +28,8 @@ namespace qsim { */ template struct KrausOperator { + using fp_type = typename Gate::fp_type; + enum Kind { kNormal = 0, kMeasurement = gate::kMeasurement, @@ -49,6 +55,68 @@ struct KrausOperator { * one operation. */ std::vector ops; + + /** + * Product of K^\dagger and K. This can be empty if unitary = true. + */ + Matrix kd_k; + + /** + * Qubits kd_k acts on. This can be empty if unitary = true. + */ + std::vector qubits; + + /** + * Calculates the product of "K^\dagger K". Sets qubits "K^\dagger K" acts on. + */ + void CalculateKdKMatrix() { + if (ops.size() == 1) { + kd_k = ops[0].matrix; + MatrixDaggerMultiply(ops[0].qubits.size(), ops[0].matrix, kd_k); + qubits = ops[0].qubits; + } else if (ops.size() > 1) { + std::set qubit_map; + + for (const auto& op : ops) { + for (unsigned q : op.qubits) { + qubit_map.insert(q); + } + } + + unsigned num_qubits = qubit_map.size(); + + qubits.resize(0); + qubits.reserve(num_qubits); + + for (auto it = qubit_map.begin(); it != qubit_map.end(); ++it) { + qubits.push_back(*it); + } + + MatrixIdentity(unsigned{1} << num_qubits, kd_k); + + for (const auto& op : ops) { + if (op.qubits.size() == num_qubits) { + MatrixMultiply(num_qubits, op.matrix, kd_k); + } else { + unsigned mask = 0; + + for (auto q : op.qubits) { + for (unsigned i = 0; i < num_qubits; ++i) { + if (q == qubits[i]) { + mask |= unsigned{1} << i; + break; + } + } + } + + MatrixMultiply(mask, op.qubits.size(), op.matrix, num_qubits, kd_k); + } + } + + auto m = kd_k; + MatrixDaggerMultiply(num_qubits, m, kd_k); + } + } }; /** diff --git a/lib/channels_cirq.h b/lib/channels_cirq.h index a8fd87ce..69f1df9d 100644 --- a/lib/channels_cirq.h +++ b/lib/channels_cirq.h @@ -237,11 +237,22 @@ struct GeneralizedAmplitudeDampingChannel { using M = Cirq::MatrixGate1; auto normal = KrausOperator>::kNormal; - - return {{normal, 0, p1, {M::Create(time, q, {t1, 0, 0, 0, 0, 0, r1, 0})}}, - {normal, 0, p2, {M::Create(time, q, {r2, 0, 0, 0, 0, 0, t2, 0})}}, - {normal, 0, p3, {M::Create(time, q, { 0, 0, s1, 0, 0, 0, 0, 0})}}, - {normal, 0, p3, {M::Create(time, q, { 0, 0, 0, 0, s2, 0, 0, 0})}}, + return {{normal, 0, p1, + {M::Create(time, q, {t1, 0, 0, 0, 0, 0, r1, 0})}, + {t1 * t1, 0, 0, 0, 0, 0, r1 * r1, 0}, {q}, + }, + {normal, 0, p2, + {M::Create(time, q, {r2, 0, 0, 0, 0, 0, t2, 0})}, + {r2 * r2, 0, 0, 0, 0, 0, t2 * t2, 0}, {q}, + }, + {normal, 0, p3, + {M::Create(time, q, {0, 0, s1, 0, 0, 0, 0, 0})}, + {0, 0, 0, 0, 0, 0, s1 * s1, 0}, {q}, + }, + {normal, 0, p3, + {M::Create(time, q, {0, 0, 0, 0, s2, 0, 0, 0})}, + {s2 * s2, 0, 0, 0, 0, 0, 0, 0}, {q}, + }, }; } @@ -281,8 +292,14 @@ struct AmplitudeDampingChannel { using M = Cirq::MatrixGate1; auto normal = KrausOperator>::kNormal; - return {{normal, 0, p1, {M::Create(time, q, {1, 0, 0, 0, 0, 0, r, 0})}}, - {normal, 0, p2, {M::Create(time, q, {0, 0, s, 0, 0, 0, 0, 0})}}, + return {{normal, 0, p1, + {M::Create(time, q, {1, 0, 0, 0, 0, 0, r, 0})}, + {1, 0, 0, 0, 0, 0, r * r, 0}, {q}, + }, + {normal, 0, p2, + {M::Create(time, q, {0, 0, s, 0, 0, 0, 0, 0})}, + {0, 0, 0, 0, 0, 0, s * s, 0}, {q}, + }, }; } @@ -320,8 +337,14 @@ struct PhaseDampingChannel { using M = Cirq::MatrixGate1; auto normal = KrausOperator>::kNormal; - return {{normal, 0, p1, {M::Create(time, q, {1, 0, 0, 0, 0, 0, r, 0})}}, - {normal, 0, p2, {M::Create(time, q, {0, 0, 0, 0, 0, 0, s, 0})}}, + return {{normal, 0, p1, + {M::Create(time, q, {1, 0, 0, 0, 0, 0, r, 0})}, + {1, 0, 0, 0, 0, 0, r * r, 0}, {q}, + }, + {normal, 0, p2, + {M::Create(time, q, {0, 0, 0, 0, 0, 0, s, 0})}, + {0, 0, 0, 0, 0, 0, s * s, 0}, {q}, + }, }; } @@ -351,8 +374,14 @@ struct ResetChannel { using M = Cirq::MatrixGate1; auto normal = KrausOperator>::kNormal; - return {{normal, 0, 0, {M::Create(time, q, {1, 0, 0, 0, 0, 0, 0, 0})}}, - {normal, 0, 0, {M::Create(time, q, {0, 0, 1, 0, 0, 0, 0, 0})}}, + return {{normal, 0, 0, + {M::Create(time, q, {1, 0, 0, 0, 0, 0, 0, 0})}, + {1, 0, 0, 0, 0, 0, 0, 0}, {q}, + }, + {normal, 0, 0, + {M::Create(time, q, {0, 0, 1, 0, 0, 0, 0, 0})}, + {0, 0, 0, 0, 0, 0, 1, 0}, {q}, + }, }; } }; diff --git a/lib/channels_qsim.h b/lib/channels_qsim.h index 9630de07..5c07bccf 100644 --- a/lib/channels_qsim.h +++ b/lib/channels_qsim.h @@ -42,8 +42,15 @@ struct AmplitudeDampingChannel { using M = GateMatrix1; auto normal = KrausOperator>::kNormal; - return {{normal, 0, p1, {M::Create(time, q, {1, 0, 0, 0, 0, 0, r, 0})}}, - {normal, 0, p2, {M::Create(time, q, {0, 0, s, 0, 0, 0, 0, 0})}}}; + return {{normal, 0, p1, + {M::Create(time, q, {1, 0, 0, 0, 0, 0, r, 0})}, + {1, 0, 0, 0, 0, 0, r * r, 0}, {q}, + }, + {normal, 0, p2, + {M::Create(time, q, {0, 0, s, 0, 0, 0, 0, 0})}, + {0, 0, 0, 0, 0, 0, s * s, 0}, {q}, + }, + }; } Channel> Create(unsigned time, unsigned q) const { @@ -79,8 +86,15 @@ struct PhaseDampingChannel { using M = GateMatrix1; auto normal = KrausOperator>::kNormal; - return {{normal, 0, p1, {M::Create(time, q, {1, 0, 0, 0, 0, 0, r, 0})}}, - {normal, 0, p2, {M::Create(time, q, {0, 0, 0, 0, 0, 0, s, 0})}}}; + return {{normal, 0, p1, + {M::Create(time, q, {1, 0, 0, 0, 0, 0, r, 0})}, + {1, 0, 0, 0, 0, 0, r * r, 0}, {q}, + }, + {normal, 0, p2, + {M::Create(time, q, {0, 0, 0, 0, 0, 0, s, 0})}, + {0, 0, 0, 0, 0, 0, s * s, 0}, {q}, + }, + }; } Channel> Create(unsigned time, unsigned q) const { diff --git a/lib/matrix.h b/lib/matrix.h index f9725eca..e126a02d 100644 --- a/lib/matrix.h +++ b/lib/matrix.h @@ -92,6 +92,39 @@ inline void MatrixMultiply( } } +/** + * Multiplies two gate matrices of equal size: m2 = m1^\dagger m2. + * @q Number of gate qubits. The number of matrix rows (columns) is 2^q. + * @m1 Matrix m1. + * @m2 Input matrix m2. Output product of matrices m2 = m1 m2. + */ +template +inline void MatrixDaggerMultiply( + unsigned q, const Matrix& m1, Matrix& m2) { + Matrix mt = m2; + unsigned n = unsigned{1} << q; + + for (unsigned i = 0; i < n; ++i) { + for (unsigned j = 0; j < n; ++j) { + fp_type2 re = 0; + fp_type2 im = 0; + + for (unsigned k = 0; k < n; ++k) { + fp_type2 r1 = m1[2 * (n * k + i)]; + fp_type2 i1 = m1[2 * (n * k + i) + 1]; + fp_type2 r2 = mt[2 * (n * k + j)]; + fp_type2 i2 = mt[2 * (n * k + j) + 1]; + + re += r1 * r2 + i1 * i2; + im += r1 * i2 - i1 * r2; + } + + m2[2 * (n * i + j)] = re; + m2[2 * (n * i + j) + 1] = im; + } + } +} + /** * Multiplies two gate matrices: m2 = m1 m2. The size of m1 should not exceed * the size of m2. diff --git a/lib/qtrajectory.h b/lib/qtrajectory.h index 773238c6..1b5c7555 100644 --- a/lib/qtrajectory.h +++ b/lib/qtrajectory.h @@ -116,7 +116,6 @@ class QuantumTrajectorySimulator { gates.reserve(4 * std::size_t(cend - cbeg)); State state = state_space.Null(); - State scratch = state_space.Null(); std::vector stat; @@ -126,7 +125,7 @@ class QuantumTrajectorySimulator { } if (!RunIteration(param, num_qubits, cbeg, cend, r, - state_space, simulator, gates, scratch, state, stat)) { + state_space, simulator, gates, state, stat)) { return false; } @@ -144,7 +143,6 @@ class QuantumTrajectorySimulator { * @param state_space StateSpace object required to manipulate state vector. * @param simulator Simulator object. Provides specific implementations for * applying gates. - * @param scratch A temporary state vector. Used for samping Kraus operators. * @param state The state of the system, to be updated by this method. * @param stat Statistics of sampled Kraus operator indices and/or measured * bitstrings, to be populated by this method. @@ -153,11 +151,10 @@ class QuantumTrajectorySimulator { static bool RunOnce(const Parameter& param, const NoisyCircuit& circuit, uint64_t r, const StateSpace& state_space, const Simulator& simulator, - State& scratch, State& state, - std::vector& stat) { + State& state, std::vector& stat) { return RunOnce(param, circuit.num_qubits, circuit.channels.begin(), circuit.channels.end(), r, state_space, simulator, - scratch, state, stat); + state, stat); } /** @@ -170,7 +167,6 @@ class QuantumTrajectorySimulator { * @param state_space StateSpace object required to manipulate state vector. * @param simulator Simulator object. Provides specific implementations for * applying gates. - * @param scratch A temporary state vector. Used for samping Kraus operators. * @param state The state of the system, to be updated by this method. * @param stat Statistics of sampled Kraus operator indices and/or measured * bitstrings, to be populated by this method. @@ -180,13 +176,13 @@ class QuantumTrajectorySimulator { ncircuit_iterator cbeg, ncircuit_iterator cend, uint64_t r, const StateSpace& state_space, - const Simulator& simulator, State& scratch, State& state, + const Simulator& simulator, State& state, std::vector& stat) { std::vector gates; gates.reserve(4 * std::size_t(cend - cbeg)); if (!RunIteration(param, num_qubits, cbeg, cend, r, - state_space, simulator, gates, scratch, state, stat)) { + state_space, simulator, gates, state, stat)) { return false; } @@ -199,7 +195,7 @@ class QuantumTrajectorySimulator { ncircuit_iterator cend, uint64_t rep, const StateSpace& state_space, const Simulator& simulator, - std::vector& gates, State& scratch, + std::vector& gates, State& state, std::vector& stat) { if (param.collect_kop_stat || param.collect_mea_stat) { stat.reserve(std::size_t(cend - cbeg)); @@ -279,14 +275,8 @@ class QuantumTrajectorySimulator { NormalizeState(!unitary, state_space, unitary, state); - if (state_space.IsNull(scratch) || scratch.num_qubits() < num_qubits) { - scratch = CreateState(num_qubits, state_space); - if (state_space.IsNull(scratch)) { - return false; - } - } - - state_space.Copy(state, scratch); + double max_prob = 0; + std::size_t max_prob_index = 0; // Perform sampling of Kraus operators using norms of updated states. for (std::size_t i = 0; i < channel.size(); ++i) { @@ -294,35 +284,29 @@ class QuantumTrajectorySimulator { if (kop.unitary) continue; - // Apply the Kraus operator. - if (kop.ops.size() == 1) { - ApplyGate(simulator, kop.ops[0], state); - } else { - DeferOps(kop.ops, gates); + double prob = std::real( + simulator.ExpectationValue(kop.qubits, kop.kd_k.data(), state)); - if (!ApplyDeferredOps(param, num_qubits, simulator, gates, state)) { - return false; - } + if (prob > max_prob) { + max_prob = prob; + max_prob_index = i; } - double n2 = state_space.Norm(state); - - cp += n2 - kop.prob; + cp += prob - kop.prob; if (r < cp || i == channel.size() - 1) { // Sample ith Kraus operator if r < cp - // Sample the first Kraus operator if r is greater than the sum of - // all probablities due to round-off errors. - uint64_t k = r < cp ? i : 0; + // Sample the highest probability Kraus operator if r is greater + // than the sum of all probablities due to round-off errors. + uint64_t k = r < cp ? i : max_prob_index; + DeferOps(channel[k].ops, gates); CollectStat(param.collect_kop_stat, k, stat); unitary = false; break; } - - state_space.Copy(scratch, state); } } diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index 3cde361b..e6788088 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -22,6 +22,7 @@ #include #include "../lib/bitstring.h" +#include "../lib/channel.h" #include "../lib/expect.h" #include "../lib/formux.h" #include "../lib/fuser_mqubit.h" @@ -338,6 +339,9 @@ void add_channel(const unsigned time, channel.emplace_back(KrausOperator{ KrausOperator::kNormal, is_unitary, prob, {gate} }); + if (!is_unitary) { + channel.back().CalculateKdKMatrix(); + } } ncircuit->channels.push_back(channel); } @@ -714,7 +718,7 @@ class SimulatorHelper { StateSpace state_space = factory.CreateStateSpace(); result = NoisyRunner::RunOnce(params, ncircuit, seed, state_space, - simulator, scratch, state, stat); + simulator, state, stat); } else { result = Runner::Run(get_params(), factory, circuit, state); } @@ -735,7 +739,7 @@ class SimulatorHelper { params, ncircuit.num_qubits, ncircuit.channels.begin() + begin, ncircuit.channels.begin() + end, - seed, state_space, simulator, scratch, state, stat + seed, state_space, simulator, state, stat ); } else { Circuit subcircuit; diff --git a/tests/BUILD b/tests/BUILD index af59659e..dadd7751 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -31,6 +31,23 @@ cc_test( ], ) +cc_test( + name = "channel_test", + srcs = ["channel_test.cc"], + deps = [ + "@com_google_googletest//:gtest_main", + "//lib:channel", + "//lib:formux", + "//lib:gates_cirq", + "//lib:matrix", + "//lib:simulator", + ], + copts = select({ + ":windows": windows_copts, + "//conditions:default": [], + }), +) + cc_test( name = "channels_cirq_test", srcs = ["channels_cirq_test.cc"], @@ -197,6 +214,7 @@ cc_library( }), deps = [ "@com_google_googletest//:gtest_main", + "//lib:channel", "//lib:channels_cirq", "//lib:circuit_noisy", "//lib:fuser_mqubit", diff --git a/tests/channel_test.cc b/tests/channel_test.cc new file mode 100644 index 00000000..15b2307e --- /dev/null +++ b/tests/channel_test.cc @@ -0,0 +1,256 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "gtest/gtest.h" + +#include "../lib/channel.h" +#include "../lib/formux.h" +#include "../lib/gates_cirq.h" +#include "../lib/matrix.h" +#include "../lib/simmux.h" + +namespace qsim { + +namespace { + +template +void TestUnitaryKrausOparator(const KrausOperator& kop) { + // Should be an identity matrix. + + unsigned m = 1 << kop.qubits.size(); + + for (unsigned i = 0; i < m; ++i) { + for (unsigned j = 0; j < m; ++j) { + auto re = kop.kd_k.data()[2 * m * i + 2 * j]; + auto im = kop.kd_k.data()[2 * m * i + 2 * j + 1]; + + if (i == j) { + EXPECT_NEAR(re, 1, 1e-6); + EXPECT_NEAR(im, 0, 1e-7); + } else { + EXPECT_NEAR(re, 0, 1e-7); + EXPECT_NEAR(im, 0, 1e-7); + } + } + } +} + +template +void TestNonUnitaryKrausOparator(const KrausOperator& kop, + const StateSpace& state_space, + const Simulator& simulator, + State& state0, State& state1) { + state_space.SetStateUniform(state0); + state_space.SetStateUniform(state1); + + for (const auto&op : kop.ops) { + simulator.ApplyGate(op.qubits, op.matrix.data(), state0); + } + + for (auto it = kop.ops.rbegin(); it != kop.ops.rend(); ++it) { + auto md = it->matrix; + MatrixDagger(1 << it->qubits.size(), md); + simulator.ApplyGate(it->qubits, md.data(), state0); + } + + simulator.ApplyGate(kop.qubits, kop.kd_k.data(), state1); + + unsigned size = unsigned{1} << (state0.num_qubits() + 1); + + for (unsigned i = 0; i < size; ++i) { + EXPECT_NEAR(state0.get()[i], state1.get()[i], 1e-7); + } +} + +} // namespace + +TEST(ChannelTest, UnitaryKdKMatrix) { + using fp_type = Simulator::fp_type; + using Gate = Cirq::GateCirq; + + auto normal = KrausOperator::kNormal; + + Channel channel = { + { + normal, 1, 0.2, { + Cirq::FSimGate::Create(0, 3, 4, 0.1, 1.4), + } + }, + { + normal, 1, 0.2, { + Cirq::rx::Create(0, 0, 0.1), + Cirq::ry::Create(0, 1, 0.2), + Cirq::FSimGate::Create(1, 0, 1, 0.2, 1.3), + } + }, + { + normal, 1, 0.2, { + Cirq::rz::Create(0, 3, 0.3), + Cirq::rx::Create(0, 1, 0.4), + Cirq::ry::Create(0, 4, 0.5), + Cirq::rz::Create(0, 0, 0.6), + } + }, + { + normal, 1, 0.2, { + Cirq::rx::Create(0, 4, 0.7), + Cirq::ry::Create(0, 3, 0.8), + Cirq::rz::Create(0, 1, 0.9), + Cirq::rx::Create(0, 0, 1.0), + Cirq::FSimGate::Create(1, 1, 3, 0.3, 1.2), + Cirq::FSimGate::Create(1, 0, 4, 0.4, 1.1), + } + }, + { + normal, 1, 0.2, { + Cirq::ry::Create(0, 7, 1.1), + Cirq::rz::Create(0, 5, 1.2), + Cirq::rx::Create(0, 1, 1.3), + Cirq::ry::Create(0, 3, 1.4), + Cirq::rz::Create(0, 2, 1.5), + Cirq::rx::Create(0, 4, 1.6), + Cirq::FSimGate::Create(1, 4, 5, 0.5, 1.0), + Cirq::FSimGate::Create(1, 1, 3, 0.6, 0.9), + Cirq::FSimGate::Create(1, 2, 7, 0.7, 0.8), + } + }, + }; + + channel[0].CalculateKdKMatrix(); + ASSERT_EQ(channel[0].kd_k.size(), 32); + ASSERT_EQ(channel[0].qubits.size(), 2); + EXPECT_EQ(channel[0].qubits[0], 3); + EXPECT_EQ(channel[0].qubits[1], 4); + TestUnitaryKrausOparator(channel[0]); + + channel[1].CalculateKdKMatrix(); + ASSERT_EQ(channel[1].kd_k.size(), 32); + ASSERT_EQ(channel[1].qubits.size(), 2); + EXPECT_EQ(channel[1].qubits[0], 0); + EXPECT_EQ(channel[1].qubits[1], 1); + TestUnitaryKrausOparator(channel[1]); + + channel[2].CalculateKdKMatrix(); + ASSERT_EQ(channel[2].kd_k.size(), 512); + ASSERT_EQ(channel[2].qubits.size(), 4); + EXPECT_EQ(channel[2].qubits[0], 0); + EXPECT_EQ(channel[2].qubits[1], 1); + EXPECT_EQ(channel[2].qubits[2], 3); + EXPECT_EQ(channel[2].qubits[3], 4); + TestUnitaryKrausOparator(channel[2]); + + channel[3].CalculateKdKMatrix(); + ASSERT_EQ(channel[3].kd_k.size(), 512); + ASSERT_EQ(channel[3].qubits.size(), 4); + EXPECT_EQ(channel[3].qubits[0], 0); + EXPECT_EQ(channel[3].qubits[1], 1); + EXPECT_EQ(channel[3].qubits[2], 3); + EXPECT_EQ(channel[3].qubits[3], 4); + TestUnitaryKrausOparator(channel[3]); + + channel[4].CalculateKdKMatrix(); + ASSERT_EQ(channel[4].kd_k.size(), 8192); + ASSERT_EQ(channel[4].qubits.size(), 6); + EXPECT_EQ(channel[4].qubits[0], 1); + EXPECT_EQ(channel[4].qubits[1], 2); + EXPECT_EQ(channel[4].qubits[2], 3); + EXPECT_EQ(channel[4].qubits[3], 4); + EXPECT_EQ(channel[4].qubits[4], 5); + EXPECT_EQ(channel[4].qubits[5], 7); + TestUnitaryKrausOparator(channel[4]); +} + +TEST(ChannelTest, NonUnitaryKdKMatrix) { + using StateSpace = Simulator::StateSpace; + using State = StateSpace::State; + using fp_type = StateSpace::fp_type; + using Gate = Cirq::GateCirq; + using M1 = Cirq::MatrixGate1; + using M2 = Cirq::MatrixGate2; + + unsigned num_qubits = 8; + auto normal = KrausOperator::kNormal; + + Channel channel = { + { + normal, 0, 0, { + M1::Create(0, 0, + {0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4}), + M1::Create(0, 1, + {0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4, 0.1}), + M2::Create(0, 0, 1, + {0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4, + 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4, 0.1, + 0.3, 0.4, 0.1, 0.2, 0.3, 0.4, 0.1, 0.2, + 0.4, 0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3}), + } + }, + { + normal, 0, 0, { + M1::Create(0, 4, + {0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4}), + M1::Create(0, 3, + {0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4, 0.1}), + M1::Create(0, 1, + {0.3, 0.4, 0.1, 0.2, 0.3, 0.4, 0.1, 0.2}), + M1::Create(0, 0, + {0.4, 0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3}), + M2::Create(0, 0, 4, + {0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4, + 0.2, 0.3, 0.4, 0.1, 0.2, 0.3, 0.4, 0.1, + 0.3, 0.4, 0.1, 0.2, 0.3, 0.4, 0.1, 0.2, + 0.4, 0.1, 0.2, 0.3, 0.4, 0.1, 0.2, 0.3}), + } + }, + }; + + StateSpace state_space(1); + Simulator simulator(1); + + State state0 = state_space.Create(num_qubits); + ASSERT_FALSE(state_space.IsNull(state0)); + + State state1 = state_space.Create(num_qubits); + ASSERT_FALSE(state_space.IsNull(state1)); + + channel[0].CalculateKdKMatrix(); + ASSERT_EQ(channel[0].kd_k.size(), 32); + ASSERT_EQ(channel[0].qubits.size(), 2); + EXPECT_EQ(channel[0].qubits[0], 0); + EXPECT_EQ(channel[0].qubits[1], 1); + TestNonUnitaryKrausOparator( + channel[0], state_space, simulator, state0, state1); + + channel[1].CalculateKdKMatrix(); + ASSERT_EQ(channel[1].kd_k.size(), 512); + ASSERT_EQ(channel[1].qubits.size(), 4); + EXPECT_EQ(channel[1].qubits[0], 0); + EXPECT_EQ(channel[1].qubits[1], 1); + EXPECT_EQ(channel[1].qubits[2], 3); + EXPECT_EQ(channel[1].qubits[3], 4); + TestNonUnitaryKrausOparator( + channel[1], state_space, simulator, state0, state1); +} + +} // namespace qsim + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/make.sh b/tests/make.sh index 9ccb228d..fb3bbc8e 100755 --- a/tests/make.sh +++ b/tests/make.sh @@ -21,6 +21,7 @@ path_to_include=googletest/googletest/include path_to_lib=googletest/googletest/make/lib g++ -O3 -I$path_to_include -L$path_to_lib -o bitstring_test.x bitstring_test.cc -lgtest -lpthread +g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o channel_test.x channel_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o channels_cirq_test.x channels_cirq_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -o circuit_qsim_parser_test.x circuit_qsim_parser_test.cc -lgtest -lpthread g++ -O3 -I$path_to_include -L$path_to_lib -mavx2 -mfma -fopenmp -o expect_nobmi2_test.x expect_test.cc -lgtest -lpthread diff --git a/tests/qtrajectory_testfixture.h b/tests/qtrajectory_testfixture.h index 80bd15a6..02588c1b 100644 --- a/tests/qtrajectory_testfixture.h +++ b/tests/qtrajectory_testfixture.h @@ -21,6 +21,7 @@ #include "gtest/gtest.h" +#include "../lib/channel.h" #include "../lib/channels_cirq.h" #include "../lib/circuit_noisy.h" #include "../lib/fuser_mqubit.h" @@ -104,6 +105,10 @@ void AddGenAmplDumpNoise1( {normal, 0, p2, {M::Create(time, q, {r2, 0, 0, 0, 0, 0, t2, 0})}}, {normal, 0, p3, {M::Create(time, q, {0, 0, s1, 0, 0, 0, 0, 0})}}, {normal, 0, p3, {M::Create(time, q, {0, 0, 0, 0, s2, 0, 0, 0})}}}); + + for (auto& kop : ncircuit.channels.back()) { + kop.CalculateKdKMatrix(); + } } template @@ -134,11 +139,103 @@ void AddGenAmplDumpNoise2( {normal, 0, p2, {M::Create(time, 0, {r2, 0, 0, 0, 0, 0, t2, 0})}}, {normal, 0, p3, {M::Create(time, 0, {0, 0, s1, 0, 0, 0, 0, 0})}}, {normal, 0, p3, {M::Create(time, 0, {0, 0, 0, 0, s2, 0, 0, 0})}}}); + + for (auto& kop : ncircuit.channels.back()) { + kop.CalculateKdKMatrix(); + } + ncircuit.channels.push_back( {{normal, 0, p1, {M::Create(time, 1, {t1, 0, 0, 0, 0, 0, r1, 0})}}, {normal, 0, p2, {M::Create(time, 1, {r2, 0, 0, 0, 0, 0, t2, 0})}}, {normal, 0, p3, {M::Create(time, 1, {0, 0, s1, 0, 0, 0, 0, 0})}}, {normal, 0, p3, {M::Create(time, 1, {0, 0, 0, 0, s2, 0, 0, 0})}}}); + + for (auto& kop : ncircuit.channels.back()) { + kop.CalculateKdKMatrix(); + } +} + +// Adds the same channel as in AddGenAmplDumpNoise2 above. +template +void AddGenAmplDumpNoise2Alt( + unsigned time, double g, NoisyCircuit& ncircuit) { + using fp_type = typename Gate::fp_type; + + // Probability of exchanging energy with the environment. + double p = 0.5; + + double p1 = p * (1 - g); + double p2 = (1 - p) * (1 - g); + double p3 = 0; + + fp_type t1 = std::sqrt(p); + fp_type r1 = std::sqrt(p * (1 - g)); + fp_type s1 = std::sqrt(p * g); + fp_type t2 = std::sqrt(1 - p); + fp_type r2 = std::sqrt((1 - p) * (1 - g)); + fp_type s2 = std::sqrt((1 - p) * g); + + auto normal = KrausOperator::kNormal; + + using M = Cirq::MatrixGate1; + + ncircuit.channels.push_back( + {{normal, 0, p1 * p1, + {M::Create(time, 0, {t1, 0, 0, 0, 0, 0, r1, 0}), + M::Create(time, 1, {t1, 0, 0, 0, 0, 0, r1, 0})}}, + {normal, 0, p1 * p2, + {M::Create(time, 0, {t1, 0, 0, 0, 0, 0, r1, 0}), + M::Create(time, 1, {r2, 0, 0, 0, 0, 0, t2, 0})}}, + {normal, 0, p1 * p3, + {M::Create(time, 0, {t1, 0, 0, 0, 0, 0, r1, 0}), + M::Create(time, 1, {0, 0, s1, 0, 0, 0, 0, 0})}}, + {normal, 0, p1 * p3, + {M::Create(time, 0, {t1, 0, 0, 0, 0, 0, r1, 0}), + M::Create(time, 1, {0, 0, 0, 0, s2, 0, 0, 0})}}, + + {normal, 0, p2 * p1, + {M::Create(time, 0, {r2, 0, 0, 0, 0, 0, t2, 0}), + M::Create(time, 1, {t1, 0, 0, 0, 0, 0, r1, 0})}}, + {normal, 0, p2 * p2, + {M::Create(time, 0, {r2, 0, 0, 0, 0, 0, t2, 0}), + M::Create(time, 1, {r2, 0, 0, 0, 0, 0, t2, 0})}}, + {normal, 0, p2 * p3, + {M::Create(time, 0, {r2, 0, 0, 0, 0, 0, t2, 0}), + M::Create(time, 1, {0, 0, s1, 0, 0, 0, 0, 0})}}, + {normal, 0, p2 * p3, + {M::Create(time, 0, {r2, 0, 0, 0, 0, 0, t2, 0}), + M::Create(time, 1, {0, 0, 0, 0, s2, 0, 0, 0})}}, + + {normal, 0, p3 * p1, + {M::Create(time, 0, {0, 0, s1, 0, 0, 0, 0, 0}), + M::Create(time, 1, {t1, 0, 0, 0, 0, 0, r1, 0})}}, + {normal, 0, p3 * p2, + {M::Create(time, 0, {0, 0, s1, 0, 0, 0, 0, 0}), + M::Create(time, 1, {r2, 0, 0, 0, 0, 0, t2, 0})}}, + {normal, 0, p3 * p3, + {M::Create(time, 0, {0, 0, s1, 0, 0, 0, 0, 0}), + M::Create(time, 1, {0, 0, s1, 0, 0, 0, 0, 0})}}, + {normal, 0, p3 * p3, + {M::Create(time, 0, {0, 0, s1, 0, 0, 0, 0, 0}), + M::Create(time, 1, {0, 0, 0, 0, s2, 0, 0, 0})}}, + + {normal, 0, p3 * p1, + {M::Create(time, 0, {0, 0, 0, 0, s2, 0, 0, 0}), + M::Create(time, 1, {t1, 0, 0, 0, 0, 0, r1, 0})}}, + {normal, 0, p3 * p2, + {M::Create(time, 0, {0, 0, 0, 0, s2, 0, 0, 0}), + M::Create(time, 1, {r2, 0, 0, 0, 0, 0, t2, 0})}}, + {normal, 0, p3 * p3, + {M::Create(time, 0, {0, 0, 0, 0, s2, 0, 0, 0}), + M::Create(time, 1, {0, 0, s1, 0, 0, 0, 0, 0})}}, + {normal, 0, p3 * p3, + {M::Create(time, 0, {0, 0, 0, 0, s2, 0, 0, 0}), + M::Create(time, 1, {0, 0, 0, 0, s2, 0, 0, 0})}}, + }); + + for (auto& kop : ncircuit.channels.back()) { + kop.CalculateKdKMatrix(); + } } template @@ -234,7 +331,6 @@ void RunOnceRepeatedly(const Factory& factory, Simulator simulator = factory.CreateSimulator(); StateSpace state_space = factory.CreateStateSpace(); - State scratch = state_space.Null(); State state = state_space.Create(num_qubits); EXPECT_FALSE(state_space.IsNull(state)); @@ -251,7 +347,7 @@ void RunOnceRepeatedly(const Factory& factory, state_space.SetStateZero(state); EXPECT_TRUE(QTSimulator::RunOnce( - param, ncircuit, i, state_space, simulator, scratch, state, stat)); + param, ncircuit, i, state_space, simulator, state, stat)); EXPECT_EQ(state_pointer, state.get()); @@ -361,9 +457,17 @@ for key, val in sorted(res.histogram(key='m').items()): using Gate = Cirq::GateCirq; - auto ncircuit = GenerateNoisyCircuit(0.1, AddGenAmplDumpNoise1, - AddGenAmplDumpNoise2); - RunOnceRepeatedly(factory, ncircuit, expected_results); + { + auto ncircuit = GenerateNoisyCircuit(0.1, AddGenAmplDumpNoise1, + AddGenAmplDumpNoise2); + RunOnceRepeatedly(factory, ncircuit, expected_results); + } + + { + auto ncircuit = GenerateNoisyCircuit(0.1, AddGenAmplDumpNoise1, + AddGenAmplDumpNoise2Alt); + RunOnceRepeatedly(factory, ncircuit, expected_results); + } } template @@ -508,7 +612,6 @@ void TestCleanCircuit(const Factory& factory) { ApplyGate(simulator, gate, state); } - State scratch = state_space.Null(); State nstate = state_space.Create(num_qubits); EXPECT_FALSE(state_space.IsNull(nstate)); @@ -522,7 +625,7 @@ void TestCleanCircuit(const Factory& factory) { // Run quantum trajectory simulator. EXPECT_TRUE(QTSimulator::RunOnce(param, num_qubits, ncircuit.channels.begin(), ncircuit.channels.end(), 0, state_space, - simulator, scratch, nstate, stat)); + simulator, nstate, stat)); EXPECT_EQ(stat.size(), 0); @@ -565,7 +668,6 @@ void TestInitialState(const Factory& factory) { Simulator simulator = factory.CreateSimulator(); StateSpace state_space = factory.CreateStateSpace(); - State scratch = state_space.Null(); State state = state_space.Create(num_qubits); EXPECT_FALSE(state_space.IsNull(state)); @@ -578,7 +680,7 @@ void TestInitialState(const Factory& factory) { } EXPECT_TRUE(QTSimulator::RunOnce( - param, ncircuit, 0, state_space, simulator, scratch, state, stat)); + param, ncircuit, 0, state_space, simulator, state, stat)); // Expect reversed order of amplitudes. for (unsigned i = 0; i < 8; ++i) { @@ -625,7 +727,6 @@ void TestUncomputeFinalState(const Factory& factory) { Simulator simulator = factory.CreateSimulator(); StateSpace state_space = factory.CreateStateSpace(); - State scratch = state_space.Null(); State state = state_space.Create(num_qubits); EXPECT_FALSE(state_space.IsNull(state)); @@ -638,8 +739,8 @@ void TestUncomputeFinalState(const Factory& factory) { std::vector stat; // Run one trajectory. - EXPECT_TRUE(QTSimulator::RunOnce(param, ncircuit, 0, state_space, simulator, - scratch, state, stat)); + EXPECT_TRUE(QTSimulator::RunOnce( + param, ncircuit, 0, state_space, simulator, state, stat)); EXPECT_EQ(ncircuit.channels.size(), stat.size()); From bc9cd459a6e279c171512123e8f40a481d56a902 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Fri, 4 Feb 2022 18:44:18 +0100 Subject: [PATCH 200/246] Reuse quantum trajectory results. --- apps/qsim_qtrajectory_cuda.cu | 26 ++- lib/qtrajectory.h | 84 +++++--- pybind_interface/pybind_main.cpp | 18 +- tests/BUILD | 1 + tests/channels_cirq_test.cc | 6 +- tests/qtrajectory_avx_test.cc | 4 + tests/qtrajectory_cuda_test.cu | 8 + tests/qtrajectory_custatevec_test.cu | 4 + tests/qtrajectory_testfixture.h | 300 +++++++++++++++++++++++++-- 9 files changed, 387 insertions(+), 64 deletions(-) diff --git a/apps/qsim_qtrajectory_cuda.cu b/apps/qsim_qtrajectory_cuda.cu index 223add5e..65fe1cd3 100644 --- a/apps/qsim_qtrajectory_cuda.cu +++ b/apps/qsim_qtrajectory_cuda.cu @@ -251,6 +251,7 @@ int main(int argc, char* argv[]) { typename QTSimulator::Parameter param3; param3.max_fused_size = opt.max_fused_size; param3.verbosity = opt.verbosity; + param3.apply_last_deferred_ops = true; auto channel1 = AmplitudeDampingChannel(opt.amplitude_damp_const); auto channel2 = PhaseDampingChannel(opt.phase_damp_const); @@ -262,6 +263,11 @@ int main(int argc, char* argv[]) { std::vector>>> results; results.reserve(opt.num_trajectories); + QTSimulator::Stat stat; + + using CleanResults = std::vector>>; + CleanResults primary_results(noisy_circuits.size()); + for (unsigned i = 0; i < opt.num_trajectories; ++i) { results.push_back({}); results[i].reserve(noisy_circuits.size()); @@ -271,7 +277,6 @@ int main(int argc, char* argv[]) { auto seed = noisy_circuits.size() * (i + opt.traj0); for (unsigned s = 0; s < noisy_circuits.size(); ++s) { - std::vector stat; if (!QTSimulator::RunOnce(param3, noisy_circuits[s], seed++, state_space, simulator, state, stat)) { return 1; @@ -280,9 +285,22 @@ int main(int argc, char* argv[]) { results[i].push_back({}); results[i][s].reserve(observables.size()); - for (const auto& obs : observables) { - results[i][s].push_back( - ExpectationValue(obs, simulator, state)); + primary_results[s].reserve(observables.size()); + + if (stat.primary && !primary_results[s].empty()) { + for (std::size_t k = 0; k < observables.size(); ++k) { + results[i][s].push_back(primary_results[s][k]); + } + } else { + for (const auto& obs : observables) { + auto result = ExpectationValue(obs, simulator, state); + results[i][s].push_back(result); + + if (stat.primary) { + primary_results[s].push_back(result); + param3.apply_last_deferred_ops = false; + } + } } } } diff --git a/lib/qtrajectory.h b/lib/qtrajectory.h index 1b5c7555..bf70d630 100644 --- a/lib/qtrajectory.h +++ b/lib/qtrajectory.h @@ -55,6 +55,29 @@ class QuantumTrajectorySimulator { * If true, normalize the state vector before performing measurements. */ bool normalize_before_mea_gates = true; + /** + * If false, do not apply deferred operators after the main loop. + * This can be used to speed up simulations of circuits with weak noise + * and without measurements by reusing the results for the "primary" noise + * realization, that is the noise realization in which the "primary" + * (highest probability) Kraus operators are sampled in each channel. + * The primary Kraus operators should be first in their respective channels. + */ + bool apply_last_deferred_ops = true; + }; + + /** + * Struct with statistics to populate by RunBatch and RunOnce methods. + */ + struct Stat { + /** + * Indices of sampled Kraus operator indices and/or measured bitstrings. + */ + std::vector samples; + /** + * True if the "primary" noise realization is sampled, false otherwise. + */ + bool primary; }; /** @@ -70,8 +93,7 @@ class QuantumTrajectorySimulator { * computing expectation values, etc). This function should have three * required parameters [repetition ID (uint64_t), final state vector * (const State&), statistics of sampled Kraus operator indices and/or - * measured bitstrings (const std::vector&)] and any number of - * optional parameters. + * measured bitstrings (const Stat&)] and any number of optional parameters. * @param args Optional arguments for the 'measure' function. * @return True if the simulation completed successfully; false otherwise. */ @@ -100,8 +122,7 @@ class QuantumTrajectorySimulator { * computing expectation values, etc). This function should have three * required parameters [repetition ID (uint64_t), final state vector * (const State&), statistics of sampled Kraus operator indices and/or - * measured bitstrings (const std::vector&)] and any number of - * optional parameters. + * measured bitstrings (const Stat&)] and any number of optional parameters. * @param args Optional arguments for the 'measure' function. * @return True if the simulation completed successfully; false otherwise. */ @@ -117,18 +138,26 @@ class QuantumTrajectorySimulator { State state = state_space.Null(); - std::vector stat; + Stat stat; + bool had_primary_realization = false; for (uint64_t r = r0; r < r1; ++r) { if (!state_space.IsNull(state)) { state_space.SetStateZero(state); } - if (!RunIteration(param, num_qubits, cbeg, cend, r, - state_space, simulator, gates, state, stat)) { + bool apply_last_deferred_ops = + param.apply_last_deferred_ops || !had_primary_realization; + + if (!RunIteration(param, apply_last_deferred_ops, num_qubits, cbeg, cend, + r, state_space, simulator, gates, state, stat)) { return false; } + if (stat.primary && !had_primary_realization) { + had_primary_realization = true; + } + measure(r, state, stat, args...); } @@ -151,7 +180,7 @@ class QuantumTrajectorySimulator { static bool RunOnce(const Parameter& param, const NoisyCircuit& circuit, uint64_t r, const StateSpace& state_space, const Simulator& simulator, - State& state, std::vector& stat) { + State& state, Stat& stat) { return RunOnce(param, circuit.num_qubits, circuit.channels.begin(), circuit.channels.end(), r, state_space, simulator, state, stat); @@ -176,13 +205,12 @@ class QuantumTrajectorySimulator { ncircuit_iterator cbeg, ncircuit_iterator cend, uint64_t r, const StateSpace& state_space, - const Simulator& simulator, State& state, - std::vector& stat) { + const Simulator& simulator, State& state, Stat& stat) { std::vector gates; gates.reserve(4 * std::size_t(cend - cbeg)); - if (!RunIteration(param, num_qubits, cbeg, cend, r, - state_space, simulator, gates, state, stat)) { + if (!RunIteration(param, param.apply_last_deferred_ops, num_qubits, cbeg, + cend, r, state_space, simulator, gates, state, stat)) { return false; } @@ -190,16 +218,17 @@ class QuantumTrajectorySimulator { } private: - static bool RunIteration(const Parameter& param, unsigned num_qubits, + static bool RunIteration(const Parameter& param, + bool apply_last_deferred_ops, unsigned num_qubits, ncircuit_iterator cbeg, ncircuit_iterator cend, uint64_t rep, const StateSpace& state_space, const Simulator& simulator, std::vector& gates, - State& state, std::vector& stat) { + State& state, Stat& stat) { if (param.collect_kop_stat || param.collect_mea_stat) { - stat.reserve(std::size_t(cend - cbeg)); - stat.resize(0); + stat.samples.reserve(std::size_t(cend - cbeg)); + stat.samples.resize(0); } if (state_space.IsNull(state)) { @@ -212,12 +241,12 @@ class QuantumTrajectorySimulator { } gates.resize(0); - stat.resize(0); RGen rgen(rep); std::uniform_real_distribution distr(0.0, 1.0); bool unitary = true; + stat.primary = true; for (auto it = cbeg; it != cend; ++it) { const auto& channel = *it; @@ -243,6 +272,8 @@ class QuantumTrajectorySimulator { CollectStat(param.collect_mea_stat, mresult.bits, stat); + stat.primary = false; + continue; } @@ -310,11 +341,13 @@ class QuantumTrajectorySimulator { } } - if (!ApplyDeferredOps(param, num_qubits, simulator, gates, state)) { - return false; - } + if (apply_last_deferred_ops || !stat.primary) { + if (!ApplyDeferredOps(param, num_qubits, simulator, gates, state)) { + return false; + } - NormalizeState(!unitary, state_space, unitary, state); + NormalizeState(!unitary, state_space, unitary, state); + } return true; } @@ -368,10 +401,13 @@ class QuantumTrajectorySimulator { } } - static void CollectStat(bool collect_stat, uint64_t i, - std::vector& stat) { + static void CollectStat(bool collect_stat, uint64_t i, Stat& stat) { if (collect_stat) { - stat.push_back(i); + stat.samples.push_back(i); + } + + if (i != 0) { + stat.primary = false; } } diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index e6788088..a6cfb812 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -481,8 +481,7 @@ std::vector> qtrajectory_simulate(const py::dict &options) { StateSpace state_space = factory.CreateStateSpace(); auto measure = [&bitstrings, &ncircuit, &litudes, &state_space]( - unsigned k, const State &state, - std::vector& stat) { + unsigned k, const State &state, Runner::Stat& stat) { for (const auto &b : bitstrings) { amplitudes.push_back(state_space.GetAmpl(state, b)); } @@ -711,7 +710,7 @@ class SimulatorHelper { bool result = false; if (is_noisy) { - std::vector stat; + NoisyRunner::Stat stat; auto params = get_noisy_params(); Simulator simulator = factory.CreateSimulator(); @@ -730,7 +729,7 @@ class SimulatorHelper { bool result = false; if (is_noisy) { - std::vector stat; + NoisyRunner::Stat stat; auto params = get_noisy_params(); Simulator simulator = factory.CreateSimulator(); StateSpace state_space = factory.CreateStateSpace(); @@ -1051,24 +1050,23 @@ std::vector qtrajectory_sample(const py::dict &options) { std::vector> results; auto measure = [&results, &ncircuit, &state_space]( - unsigned k, const State &state, - std::vector& stat) { + unsigned k, const State& state, Runner::Stat& stat) { // Converts stat (which matches the MeasurementResult 'bits' field) into // bitstrings matching the MeasurementResult 'bitstring' field. unsigned idx = 0; for (const auto& channel : ncircuit.channels) { if (channel[0].kind != gate::kMeasurement) continue; - for (const auto &op : channel[0].ops) { + for (const auto& op : channel[0].ops) { std::vector bitstring; - uint64_t val = stat[idx]; - for (const auto &q : op.qubits) { + uint64_t val = stat.samples[idx]; + for (const auto& q : op.qubits) { bitstring.push_back((val >> q) & 1); } results.push_back(bitstring); idx += 1; - if (idx >= stat.size()) + if (idx >= stat.samples.size()) return; } } diff --git a/tests/BUILD b/tests/BUILD index dadd7751..a6c05c96 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -217,6 +217,7 @@ cc_library( "//lib:channel", "//lib:channels_cirq", "//lib:circuit_noisy", + "//lib:expect", "//lib:fuser_mqubit", "//lib:gate_appl", "//lib:gates_cirq", diff --git a/tests/channels_cirq_test.cc b/tests/channels_cirq_test.cc index b7bdade5..dedb922e 100644 --- a/tests/channels_cirq_test.cc +++ b/tests/channels_cirq_test.cc @@ -68,10 +68,10 @@ void RunBatch(const NoisyCircuit& ncircuit, unsigned num_threads = 1; auto measure = [](uint64_t r, const State& state, - const std::vector& stat, + const QTSimulator::Stat& stat, std::vector& histogram) { - ASSERT_EQ(stat.size(), 1); - ++histogram[stat[0]]; + ASSERT_EQ(stat.samples.size(), 1); + ++histogram[stat.samples[0]]; }; std::vector histogram(1 << num_qubits, 0); diff --git a/tests/qtrajectory_avx_test.cc b/tests/qtrajectory_avx_test.cc index b4a31baa..2dc126d3 100644 --- a/tests/qtrajectory_avx_test.cc +++ b/tests/qtrajectory_avx_test.cc @@ -44,6 +44,10 @@ TEST(QTrajectoryAVXTest, GenDump) { TestGenDump(qsim::Factory()); } +TEST(QTrajectoryAVXTest, ReusingResults) { + TestReusingResults(qsim::Factory()); +} + TEST(QTrajectoryAVXTest, CollectKopStat) { TestCollectKopStat(qsim::Factory()); } diff --git a/tests/qtrajectory_cuda_test.cu b/tests/qtrajectory_cuda_test.cu index 66c31acb..730ff7ed 100644 --- a/tests/qtrajectory_cuda_test.cu +++ b/tests/qtrajectory_cuda_test.cu @@ -58,6 +58,14 @@ TEST(QTrajectoryCUDATest, GenDump) { TestGenDump(factory); } +TEST(QTrajectoryCUDATest, ReusingResults) { + using Factory = qsim::Factory; + Factory::StateSpace::Parameter param1; + Factory::Simulator::Parameter param2; + Factory factory(param1, param2); + TestReusingResults(factory); +} + TEST(QTrajectoryCUDATest, CollectKopStat) { using Factory = qsim::Factory; Factory::StateSpace::Parameter param1; diff --git a/tests/qtrajectory_custatevec_test.cu b/tests/qtrajectory_custatevec_test.cu index c8a2958d..ca7e823b 100644 --- a/tests/qtrajectory_custatevec_test.cu +++ b/tests/qtrajectory_custatevec_test.cu @@ -59,6 +59,10 @@ TEST(QTrajectoryCuStateVecTest, GenDump) { TestGenDump(qsim::Factory()); } +TEST(QTrajectoryCuStateVecTest, ReusingResults) { + TestReusingResults(qsim::Factory()); +} + TEST(QTrajectoryCuStateVecTest, CollectKopStat) { TestCollectKopStat(qsim::Factory()); } diff --git a/tests/qtrajectory_testfixture.h b/tests/qtrajectory_testfixture.h index 02588c1b..786d997c 100644 --- a/tests/qtrajectory_testfixture.h +++ b/tests/qtrajectory_testfixture.h @@ -24,6 +24,7 @@ #include "../lib/channel.h" #include "../lib/channels_cirq.h" #include "../lib/circuit_noisy.h" +#include "../lib/expect.h" #include "../lib/fuser_mqubit.h" #include "../lib/gate_appl.h" #include "../lib/gates_cirq.h" @@ -238,9 +239,66 @@ void AddGenAmplDumpNoise2Alt( } } +template +void AddAmplDumpNoise1( + unsigned time, unsigned q, double g, NoisyCircuit& ncircuit) { + using fp_type = typename Gate::fp_type; + + double p1 = 1 - g; + double p2 = 0; + + fp_type r = std::sqrt(p1); + fp_type s = std::sqrt(g); + + auto normal = KrausOperator::kNormal; + + using M = Cirq::MatrixGate1; + + ncircuit.channels.push_back( + {{normal, 0, p1, {M::Create(time, q, {1, 0, 0, 0, 0, 0, r, 0})}}, + {normal, 0, p2, {M::Create(time, q, {0, 0, s, 0, 0, 0, 0, 0})}}}); + + for (auto& kop : ncircuit.channels.back()) { + kop.CalculateKdKMatrix(); + } +} + +template +void AddAmplDumpNoise2( + unsigned time, double g, NoisyCircuit& ncircuit) { + using fp_type = typename Gate::fp_type; + + double p1 = 1 - g; + double p2 = 0; + + fp_type r = std::sqrt(p1); + fp_type s = std::sqrt(g); + + auto normal = KrausOperator::kNormal; + + using M = Cirq::MatrixGate1; + + ncircuit.channels.push_back( + {{normal, 0, p1, {M::Create(time, 0, {1, 0, 0, 0, 0, 0, r, 0})}}, + {normal, 0, p2, {M::Create(time, 0, {0, 0, s, 0, 0, 0, 0, 0})}}}); + + for (auto& kop : ncircuit.channels.back()) { + kop.CalculateKdKMatrix(); + } + + ncircuit.channels.push_back( + {{normal, 0, p1, {M::Create(time, 1, {1, 0, 0, 0, 0, 0, r, 0})}}, + {normal, 0, p2, {M::Create(time, 1, {0, 0, s, 0, 0, 0, 0, 0})}}}); + + for (auto& kop : ncircuit.channels.back()) { + kop.CalculateKdKMatrix(); + } +} + template NoisyCircuit GenerateNoisyCircuit( - double p, AddNoise1&& add_noise1, AddNoise2&& add_noise2) { + double p, AddNoise1&& add_noise1, AddNoise2&& add_noise2, + bool add_measurement = true) { using fp_type = typename Gate::fp_type; NoisyCircuit ncircuit; @@ -273,9 +331,12 @@ NoisyCircuit GenerateNoisyCircuit( add_noise1(9, 1, p, ncircuit); ncircuit.channels.push_back({{normal, 1, 1.0, {IS::Create(10, 0, 1)}}}); add_noise2(11, p, ncircuit); - ncircuit.channels.push_back({{KrausOperator::kMeasurement, 1, 1.0, - {gate::Measurement::Create(12, {0, 1})}}}); - add_noise2(13, p, ncircuit); + if (add_measurement) { + ncircuit.channels.push_back( + {{KrausOperator::kMeasurement, 1, 1.0, + {gate::Measurement::Create(12, {0, 1})}}}); + add_noise2(13, p, ncircuit); + } return ncircuit; } @@ -293,10 +354,10 @@ void RunBatch(const Factory& factory, const NoisyCircuit& ncircuit, unsigned num_reps = 25000; auto measure = [](uint64_t r, const State& state, - const std::vector& stat, + const typename QTSimulator::Stat& stat, std::vector& histogram) { - ASSERT_EQ(stat.size(), 1); - ++histogram[stat[0]]; + ASSERT_EQ(stat.samples.size(), 1); + ++histogram[stat.samples[0]]; }; std::vector histogram(1 << num_qubits, 0); @@ -334,9 +395,7 @@ void RunOnceRepeatedly(const Factory& factory, State state = state_space.Create(num_qubits); EXPECT_FALSE(state_space.IsNull(state)); - auto state_pointer = state.get(); - - std::vector stat; + typename QTSimulator::Stat stat; std::vector histogram(1 << num_qubits, 0); @@ -349,10 +408,8 @@ void RunOnceRepeatedly(const Factory& factory, EXPECT_TRUE(QTSimulator::RunOnce( param, ncircuit, i, state_space, simulator, state, stat)); - EXPECT_EQ(state_pointer, state.get()); - - ASSERT_EQ(stat.size(), 1); - ++histogram[stat[0]]; + ASSERT_EQ(stat.samples.size(), 1); + ++histogram[stat.samples[0]]; } for (std::size_t i = 0; i < histogram.size(); ++i) { @@ -360,6 +417,181 @@ void RunOnceRepeatedly(const Factory& factory, } } +template +std::vector> ExpValsRunBatch( + const Factory& factory, const NoisyCircuit& ncircuit, + bool reuse_results) { + using Simulator = typename Factory::Simulator; + using StateSpace = typename Factory::StateSpace; + using State = typename StateSpace::State; + using Fuser = MultiQubitGateFuser; + using QTSimulator = QuantumTrajectorySimulator; + + unsigned num_qubits = 2; + unsigned num_reps = 25000; + + Simulator simulator = factory.CreateSimulator(); + StateSpace state_space = factory.CreateStateSpace(); + + State state = state_space.Create(num_qubits); + EXPECT_FALSE(state_space.IsNull(state)); + + typename QTSimulator::Stat stat; + + typename QTSimulator::Parameter param; + param.apply_last_deferred_ops = !reuse_results; + + using Observables = std::vector>>; + Observables observables; + observables.reserve(num_qubits); + + using rx = qsim::Cirq::rx; + + for (unsigned q = 0; q < num_qubits; ++q) { + observables.push_back({{{1.0, 0.0}, {rx::Create(0, q, 1.7 + 0.6 * q)}}}); + } + + using TrajResults = std::vector>>; + TrajResults traj_results(observables.size()); + + for (std::size_t k = 0; k < observables.size(); ++k) { + traj_results[k].reserve(num_reps); + } + + std::vector> primary_results; + primary_results.reserve(observables.size()); + + auto measure = [](uint64_t r, const State& state, + const typename QTSimulator::Stat& stat, + const Simulator& simulator, bool reuse_results, + const Observables& observables, + std::vector>& primary_results, + TrajResults& traj_results) { + if (reuse_results && stat.primary && !primary_results.empty()) { + for (std::size_t k = 0; k < observables.size(); ++k) { + traj_results[k].push_back(primary_results[k]); + } + } else { + for (std::size_t k = 0; k < observables.size(); ++k) { + const auto& obs = observables[k]; + auto result = ExpectationValue(obs, simulator, state); + traj_results[k].push_back(result); + + if (reuse_results && stat.primary) { + primary_results.push_back(result); + } + } + } + }; + + EXPECT_TRUE(QTSimulator::RunBatch(param, ncircuit, 0, num_reps, state_space, + simulator, measure, simulator, + reuse_results, observables, primary_results, + traj_results)); + + std::vector> results; + results.reserve(observables.size()); + + double inverse_num_reps = 1.0 / num_reps; + + for (std::size_t k = 0; k < observables.size(); ++k) { + std::complex sum = 0; + for (unsigned i = 0; i < num_reps; ++i) { + sum += traj_results[k][i]; + } + + results.push_back(inverse_num_reps * sum); + } + + return results; +} + +template +std::vector> ExpValsRunOnceRepeatedly( + const Factory& factory, const NoisyCircuit& ncircuit, + bool reuse_results) { + using Simulator = typename Factory::Simulator; + using StateSpace = typename Factory::StateSpace; + using State = typename StateSpace::State; + using Fuser = MultiQubitGateFuser; + using QTSimulator = QuantumTrajectorySimulator; + + unsigned num_qubits = 2; + unsigned num_reps = 25000; + + Simulator simulator = factory.CreateSimulator(); + StateSpace state_space = factory.CreateStateSpace(); + + State state = state_space.Create(num_qubits); + EXPECT_FALSE(state_space.IsNull(state)); + + typename QTSimulator::Stat stat; + + typename QTSimulator::Parameter param; + param.apply_last_deferred_ops = true; + + std::vector>> observables; + observables.reserve(num_qubits); + + using rx = qsim::Cirq::rx; + + for (unsigned q = 0; q < num_qubits; ++q) { + observables.push_back({{{1.0, 0.0}, {rx::Create(0, q, 1.7 + 0.6 * q)}}}); + } + + using TrajResults = std::vector>>; + TrajResults traj_results(observables.size()); + + for (std::size_t k = 0; k < observables.size(); ++k) { + traj_results[k].reserve(num_reps); + } + + std::vector> primary_results; + primary_results.reserve(observables.size()); + + for (unsigned i = 0; i < num_reps; ++i) { + state_space.SetStateZero(state); + + EXPECT_TRUE(QTSimulator::RunOnce( + param, ncircuit, i, state_space, simulator, state, stat)); + + if (reuse_results && stat.primary && !primary_results.empty()) { + for (std::size_t k = 0; k < observables.size(); ++k) { + traj_results[k].push_back(primary_results[k]); + } + } else { + for (std::size_t k = 0; k < observables.size(); ++k) { + const auto& obs = observables[k]; + auto result = ExpectationValue(obs, simulator, state); + traj_results[k].push_back(result); + + if (reuse_results && stat.primary) { + primary_results.push_back(result); + param.apply_last_deferred_ops = false; + } + } + } + } + + std::vector> results; + results.reserve(observables.size()); + + double inverse_num_reps = 1.0 / num_reps; + + for (std::size_t k = 0; k < observables.size(); ++k) { + std::complex sum = 0; + for (unsigned i = 0; i < num_reps; ++i) { + sum += traj_results[k][i]; + } + + results.push_back(inverse_num_reps * sum); + } + + return results; +} + template void TestBitFlip(const Factory& factory) { /* The expected results are obtained with the following Cirq code. @@ -470,6 +702,28 @@ for key, val in sorted(res.histogram(key='m').items()): } } +template +void TestReusingResults(const Factory& factory) { + using Gate = Cirq::GateCirq; + + auto ncircuit = GenerateNoisyCircuit(0.02, AddAmplDumpNoise1, + AddAmplDumpNoise2, false); + + auto results1 = ExpValsRunOnceRepeatedly(factory, ncircuit, false); + auto results2 = ExpValsRunOnceRepeatedly(factory, ncircuit, true); + auto results3 = ExpValsRunBatch(factory, ncircuit, false); + auto results4 = ExpValsRunBatch(factory, ncircuit, true); + + for (std::size_t k = 0; k < results1.size(); ++k) { + EXPECT_NEAR(std::real(results1[k]), std::real(results2[k]), 1e-8); + EXPECT_NEAR(std::imag(results1[k]), std::imag(results2[k]), 1e-8); + EXPECT_NEAR(std::real(results1[k]), std::real(results3[k]), 1e-8); + EXPECT_NEAR(std::imag(results1[k]), std::imag(results3[k]), 1e-8); + EXPECT_NEAR(std::real(results1[k]), std::real(results4[k]), 1e-8); + EXPECT_NEAR(std::imag(results1[k]), std::imag(results4[k]), 1e-8); + } +} + template void TestCollectKopStat(const Factory& factory) { using Simulator = typename Factory::Simulator; @@ -515,11 +769,11 @@ void TestCollectKopStat(const Factory& factory) { {normal, 1, p2, {X::Create(1, 3)}}}); auto measure = [](uint64_t r, const State& state, - const std::vector& stat, + const typename QTSimulator::Stat& stat, std::vector>& histogram) { - ASSERT_EQ(stat.size(), histogram.size()); + ASSERT_EQ(stat.samples.size(), histogram.size()); for (std::size_t i = 0; i < histogram.size(); ++i) { - ++histogram[i][stat[i]]; + ++histogram[i][stat.samples[i]]; } }; @@ -616,7 +870,7 @@ void TestCleanCircuit(const Factory& factory) { EXPECT_FALSE(state_space.IsNull(nstate)); - std::vector stat; + typename QTSimulator::Stat stat; typename QTSimulator::Parameter param; @@ -627,7 +881,7 @@ void TestCleanCircuit(const Factory& factory) { ncircuit.channels.end(), 0, state_space, simulator, nstate, stat)); - EXPECT_EQ(stat.size(), 0); + EXPECT_EQ(stat.samples.size(), 0); for (uint64_t i = 0; i < size; ++i) { auto a1 = state_space.GetAmpl(state, i); @@ -673,7 +927,7 @@ void TestInitialState(const Factory& factory) { EXPECT_FALSE(state_space.IsNull(state)); typename QTSimulator::Parameter param; - std::vector stat; + typename QTSimulator::Stat stat; for (unsigned i = 0; i < 8; ++i) { state_space.SetAmpl(state, i, 1 + i, 0); @@ -736,19 +990,19 @@ void TestUncomputeFinalState(const Factory& factory) { typename QTSimulator::Parameter param; param.collect_kop_stat = true; - std::vector stat; + typename QTSimulator::Stat stat; // Run one trajectory. EXPECT_TRUE(QTSimulator::RunOnce( param, ncircuit, 0, state_space, simulator, state, stat)); - EXPECT_EQ(ncircuit.channels.size(), stat.size()); + EXPECT_EQ(ncircuit.channels.size(), stat.samples.size()); // Uncompute the final state back to |0000> (up to round-off errors). for (std::size_t i = 0; i < ncircuit.channels.size(); ++i) { auto k = ncircuit.channels.size() - 1 - i; - const auto& ops = ncircuit.channels[k][stat[k]].ops; + const auto& ops = ncircuit.channels[k][stat.samples[k]].ops; for (auto it = ops.rbegin(); it != ops.rend(); ++it) { ApplyGateDagger(simulator, *it, state); From 83d076d7c5363f0b53e7ed35a066bfb3ad54a744 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 4 Feb 2022 10:46:22 -0800 Subject: [PATCH 201/246] Avoid numpy size limits. --- qsimcirq/qsim_simulator.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index c52b8474..77e6775f 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -99,7 +99,7 @@ def _needs_trajectories(circuit: cirq.Circuit) -> bool: op, {param: 1 for param in cirq.parameter_names(op)} ) ) - if not (cirq.has_unitary(test_op) or cirq.is_measurement(test_op)): + if not (cirq.is_measurement(test_op) or cirq.has_unitary(test_op)): return True return False @@ -356,7 +356,12 @@ def _sample_measure_results( translator_fn_name = "translate_cirq_to_qsim" sampler_fn = self._sim_module.qsim_sample - if not noisy and program.are_all_measurements_terminal() and repetitions > 1: + if ( + not noisy and + program.are_all_measurements_terminal() and + repetitions > 1 and + num_qubits <= 32 # max length of ndarray.shape + ): # Measurements must be replaced with identity gates to sample properly. # Simply removing them may omit qubits from the circuit. for i in range(len(program.moments)): From e4c988177261ffefdbeb3644b80d09b69ee3512c Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 4 Feb 2022 11:09:29 -0800 Subject: [PATCH 202/246] format --- qsimcirq/qsim_simulator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 77e6775f..a3feaf27 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -357,10 +357,10 @@ def _sample_measure_results( sampler_fn = self._sim_module.qsim_sample if ( - not noisy and - program.are_all_measurements_terminal() and - repetitions > 1 and - num_qubits <= 32 # max length of ndarray.shape + not noisy + and program.are_all_measurements_terminal() + and repetitions > 1 + and num_qubits <= 32 # max length of ndarray.shape ): # Measurements must be replaced with identity gates to sample properly. # Simply removing them may omit qubits from the circuit. From 96bcb3fde71e78b7abfcde25a4098d2528bdf4af Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Tue, 8 Feb 2022 17:00:06 +0100 Subject: [PATCH 203/246] Add clarifications. --- lib/qtrajectory.h | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/qtrajectory.h b/lib/qtrajectory.h index bf70d630..c16970c3 100644 --- a/lib/qtrajectory.h +++ b/lib/qtrajectory.h @@ -56,12 +56,18 @@ class QuantumTrajectorySimulator { */ bool normalize_before_mea_gates = true; /** - * If false, do not apply deferred operators after the main loop. - * This can be used to speed up simulations of circuits with weak noise - * and without measurements by reusing the results for the "primary" noise - * realization, that is the noise realization in which the "primary" - * (highest probability) Kraus operators are sampled in each channel. - * The primary Kraus operators should be first in their respective channels. + * If false, do not apply deferred operators after the main loop for + * the "primary" noise trajectory, that is the trajectory in which + * the primary (the first operators in their respective channels) Kraus + * operators are sampled for each channel and there are no measurements + * in the computational basis. This can be used to speed up simulations + * of circuits with weak noise and without measurements by reusing + * the primary trajectory results. It is the client's responsibility + * to collect the primary trajectory results and to reuse them. There is + * an additional condition for RunBatch. In this case, the deferred + * operators after the main loop are still applied for the first occurence + * of the primary trajectory. The primary Kraus operators should have + * the highest sampling probabilities to achieve the highest speedup. */ bool apply_last_deferred_ops = true; }; @@ -75,7 +81,7 @@ class QuantumTrajectorySimulator { */ std::vector samples; /** - * True if the "primary" noise realization is sampled, false otherwise. + * True if the "primary" noise trajectory is sampled, false otherwise. */ bool primary; }; From 51af09d18dae1a77debae731cbc7cf8ce3e4e15d Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Tue, 8 Feb 2022 20:55:02 +0100 Subject: [PATCH 204/246] Recommended fixes. --- lib/qtrajectory.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/qtrajectory.h b/lib/qtrajectory.h index c16970c3..17cd83fb 100644 --- a/lib/qtrajectory.h +++ b/lib/qtrajectory.h @@ -62,12 +62,14 @@ class QuantumTrajectorySimulator { * operators are sampled for each channel and there are no measurements * in the computational basis. This can be used to speed up simulations * of circuits with weak noise and without measurements by reusing - * the primary trajectory results. It is the client's responsibility - * to collect the primary trajectory results and to reuse them. There is - * an additional condition for RunBatch. In this case, the deferred - * operators after the main loop are still applied for the first occurence - * of the primary trajectory. The primary Kraus operators should have - * the highest sampling probabilities to achieve the highest speedup. + * the primary trajectory results. There is an additional condition for + * RunBatch. In this case, the deferred operators after the main loop are + * still applied for the first occurence of the primary trajectory. + * The primary Kraus operators should have the highest sampling + * probabilities to achieve the highest speedup. + * + * It is the client's responsibility to collect the primary trajectory + * results and to reuse them. */ bool apply_last_deferred_ops = true; }; From c3e549f66d93e3d9c55333e8e288de140ea8d3ff Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 9 Feb 2022 15:13:28 -0800 Subject: [PATCH 205/246] Use qsim for repeated sampling. --- pybind_interface/pybind_main.cpp | 24 +++++++++++++++++++++++ pybind_interface/pybind_main.h | 12 ++++++++++++ qsimcirq/qsim_simulator.py | 33 ++++++++++++++------------------ 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/pybind_interface/pybind_main.cpp b/pybind_interface/pybind_main.cpp index e6788088..51423be0 100644 --- a/pybind_interface/pybind_main.cpp +++ b/pybind_interface/pybind_main.cpp @@ -526,6 +526,15 @@ class SimulatorHelper { return helper.release_state_to_python(); } + static std::vector sample_final_state( + const py::dict &options, bool is_noisy, uint64_t num_samples) { + auto helper = SimulatorHelper(options, is_noisy); + if (!helper.is_valid || !helper.simulate(0)) { + return {}; + } + return helper.sample(num_samples); + } + template static std::vector> simulate_expectation_values( const py::dict &options, @@ -754,6 +763,11 @@ class SimulatorHelper { return result; } + std::vector sample(uint64_t num_samples) { + StateSpace state_space = factory.CreateStateSpace(); + return state_space.Sample(state, num_samples, seed); + } + py::array_t release_state_to_python() { StateSpace state_space = factory.CreateStateSpace(); state_space.InternalToNormalOrder(state); @@ -932,6 +946,16 @@ qtrajectory_simulate_moment_expectation_values( // Methods for sampling. +std::vector qsim_sample_final( + const py::dict &options, uint64_t num_samples) { + return SimulatorHelper::sample_final_state(options, false, num_samples); +} + +std::vector qtrajectory_sample_final( + const py::dict &options, uint64_t num_samples) { + return SimulatorHelper::sample_final_state(options, true, num_samples); +} + std::vector qsim_sample(const py::dict &options) { Circuit> circuit; try { diff --git a/pybind_interface/pybind_main.h b/pybind_interface/pybind_main.h index aed6b615..3263fdd8 100644 --- a/pybind_interface/pybind_main.h +++ b/pybind_interface/pybind_main.h @@ -90,6 +90,8 @@ py::array_t qsim_simulate_fullstate( const py::dict &options, const py::array_t &input_vector); std::vector qsim_sample(const py::dict &options); +std::vector qsim_sample_final( + const py::dict &options, uint64_t num_samples); // Methods for simulating noisy circuits. std::vector> qtrajectory_simulate(const py::dict &options); @@ -100,6 +102,8 @@ py::array_t qtrajectory_simulate_fullstate( const py::dict &options, const py::array_t &input_vector); std::vector qtrajectory_sample(const py::dict &options); +std::vector qtrajectory_sample_final( + const py::dict &options, uint64_t num_samples); // As above, but returning expectation values instead. std::vector> qsim_simulate_expectation_values( @@ -200,8 +204,12 @@ std::vector> qsimh_simulate(const py::dict &options); \ /* Methods for returning samples */ \ m.def("qsim_sample", &qsim_sample, "Call the qsim sampler"); \ + m.def("qsim_sample_final", &qsim_sample_final, \ + "Call the qsim final-state sampler"); \ m.def("qtrajectory_sample", &qtrajectory_sample, \ "Call the qtrajectory sampler"); \ + m.def("qtrajectory_sample_final", &qtrajectory_sample_final, \ + "Call the qtrajectory final-state sampler"); \ \ using GateCirq = qsim::Cirq::GateCirq; \ using OpString = qsim::OpString; \ @@ -400,8 +408,12 @@ std::vector> qsimh_simulate(const py::dict &options); \ /* Methods for returning samples */ \ m.def("qsim_sample", &qsim_sample, "Call the qsim sampler"); \ + m.def("qsim_sample_final", &qsim_sample_final, \ + "Call the qsim final-state sampler"); \ m.def("qtrajectory_sample", &qtrajectory_sample, \ "Call the qtrajectory sampler"); \ + m.def("qtrajectory_sample_final", &qtrajectory_sample_final, \ + "Call the qtrajectory final-state sampler"); \ \ using GateCirq = qsim::Cirq::GateCirq; \ using OpString = qsim::OpString; \ diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index a3feaf27..935cfd75 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -349,19 +349,7 @@ def _sample_measure_results( ) noisy = _needs_trajectories(program) - if noisy: - translator_fn_name = "translate_cirq_to_qtrajectory" - sampler_fn = self._sim_module.qtrajectory_sample - else: - translator_fn_name = "translate_cirq_to_qsim" - sampler_fn = self._sim_module.qsim_sample - - if ( - not noisy - and program.are_all_measurements_terminal() - and repetitions > 1 - and num_qubits <= 32 # max length of ndarray.shape - ): + if not noisy and program.are_all_measurements_terminal() and repetitions > 1: # Measurements must be replaced with identity gates to sample properly. # Simply removing them may omit qubits from the circuit. for i in range(len(program.moments)): @@ -378,12 +366,12 @@ def _sample_measure_results( cirq.QubitOrder.DEFAULT, ) options["s"] = self.get_seed() - final_state = self._sim_module.qsim_simulate_fullstate(options, 0) - full_results = cirq.sample_state_vector( - final_state.view(np.complex64), - range(num_qubits), - repetitions=repetitions, - seed=self._prng, + raw_results = self._sim_module.qsim_sample_final(options, repetitions) + full_results = np.array( + [ + [bool(result & (1 << q)) for q in reversed(range(num_qubits))] + for result in raw_results + ] ) for key, op in meas_ops.items(): @@ -393,6 +381,13 @@ def _sample_measure_results( results[key] = full_results[:, meas_indices] ^ invert_mask else: + if noisy: + translator_fn_name = "translate_cirq_to_qtrajectory" + sampler_fn = self._sim_module.qtrajectory_sample + else: + translator_fn_name = "translate_cirq_to_qsim" + sampler_fn = self._sim_module.qsim_sample + options["c"], _ = self._translate_circuit( program, translator_fn_name, From 90a88e77c8dafa0ffc762c3696770dbee4fe3cca Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 16 Feb 2022 09:55:18 -0800 Subject: [PATCH 206/246] Update version to v0.12.0 --- qsimcirq/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq/_version.py b/qsimcirq/_version.py index c95999d5..4016bd7b 100644 --- a/qsimcirq/_version.py +++ b/qsimcirq/_version.py @@ -1,3 +1,3 @@ """The version number defined here is read automatically in setup.py.""" -__version__ = "0.11.2.dev20220104" +__version__ = "0.12.0" From b4d77b1dc9af2a483b02f43bb87ca26d633e1093 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Fri, 18 Feb 2022 16:22:55 +0100 Subject: [PATCH 207/246] cuQuantum lib directory. --- Makefile | 2 +- apps/make.sh | 2 +- pybind_interface/custatevec/CMakeLists.txt | 2 +- tests/make.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 706ffab6..7cdaa414 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ ARCHFLAGS = -march=native NVCCFLAGS = -O3 # CUQUANTUM_DIR should be set. -CUSTATEVECFLAGS = -I$(CUQUANTUM_DIR)/include -L$(CUQUANTUM_DIR)/lib64 -lcustatevec -lcublas +CUSTATEVECFLAGS = -I$(CUQUANTUM_DIR)/include -L${CUQUANTUM_DIR}/lib -L$(CUQUANTUM_DIR)/lib64 -lcustatevec -lcublas PYBIND11 = true diff --git a/apps/make.sh b/apps/make.sh index 7b2db2c8..c742e192 100755 --- a/apps/make.sh +++ b/apps/make.sh @@ -27,5 +27,5 @@ nvcc -O3 -o qsim_base_cuda.x qsim_base_cuda.cu nvcc -O3 -o qsim_qtrajectory_cuda.x qsim_qtrajectory_cuda.cu # CUQUANTUM_DIR should be set. -CUSTATEVECFLAGS="-I${CUQUANTUM_DIR}/include -L${CUQUANTUM_DIR}/lib64 -lcustatevec -lcublas" +CUSTATEVECFLAGS="-I${CUQUANTUM_DIR}/include -L${CUQUANTUM_DIR}/lib -L${CUQUANTUM_DIR}/lib64 -lcustatevec -lcublas" nvcc -O3 $CUSTATEVECFLAGS -o qsim_base_custatevec.x qsim_base_custatevec.cu diff --git a/pybind_interface/custatevec/CMakeLists.txt b/pybind_interface/custatevec/CMakeLists.txt index 0d9a05fb..912f531c 100644 --- a/pybind_interface/custatevec/CMakeLists.txt +++ b/pybind_interface/custatevec/CMakeLists.txt @@ -46,7 +46,7 @@ find_package(CUDA REQUIRED) include_directories(${PYTHON_INCLUDE_DIRS} ${pybind11_SOURCE_DIR}/include) include_directories($ENV{CUQUANTUM_DIR}/include) -link_directories($ENV{CUQUANTUM_DIR}/lib64) +link_directories($ENV{CUQUANTUM_DIR}/lib $ENV{CUQUANTUM_DIR}/lib64) cuda_add_library(qsim_custatevec MODULE pybind_main_custatevec.cpp) target_link_libraries(qsim_custatevec -lcustatevec -lcublas) diff --git a/tests/make.sh b/tests/make.sh index fb3bbc8e..f379a102 100755 --- a/tests/make.sh +++ b/tests/make.sh @@ -61,7 +61,7 @@ nvcc -O3 -I$path_to_include -L$path_to_lib -o simulator_cuda_test.x simulator_cu nvcc -O3 -I$path_to_include -L$path_to_lib -o statespace_cuda_test.x statespace_cuda_test.cu -lgtest -lpthread # CUQUANTUM_DIR should be set. -CUSTATEVECFLAGS="-I${CUQUANTUM_DIR}/include -L${CUQUANTUM_DIR}/lib64 -lcustatevec -lcublas" +CUSTATEVECFLAGS="-I${CUQUANTUM_DIR}/include -L${CUQUANTUM_DIR}/lib -L${CUQUANTUM_DIR}/lib64 -lcustatevec -lcublas" nvcc -O3 $CUSTATEVECFLAGS -I$path_to_include -L$path_to_lib -o hybrid_custatevec_test.x hybrid_custatevec_test.cu -lgtest -lpthread nvcc -O3 $CUSTATEVECFLAGS -I$path_to_include -L$path_to_lib -o qtrajectory_custatevec_test.x qtrajectory_custatevec_test.cu -lgtest -lpthread nvcc -O3 $CUSTATEVECFLAGS -I$path_to_include -L$path_to_lib -o simulator_custatevec_test.x simulator_custatevec_test.cu -lgtest -lpthread From a030893f2b773cb5b9b6b19a15358b4cdd31729f Mon Sep 17 00:00:00 2001 From: Jan Hosang Date: Tue, 22 Feb 2022 16:37:53 +0100 Subject: [PATCH 208/246] Fix comparator bug which are not compliant to the strict weak ordering std::sort requries a strict weak ordering, but the comparator was reflexive (x < x). --- lib/fuser_mqubit.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index 4ab4f100..e08a5b84 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -954,8 +954,11 @@ class MultiQubitGateFuser final : public Fuser { if (ln != nullptr && rn != nullptr) { return R()(ln->val->parent->time, rn->val->parent->time); - } else { + } else if (ln != rn) { return ln != nullptr || rn == nullptr; + } else { + // Comparator needs to be irreflexive to get a strict weak ordering. + return false; } }); From c73c1d97771eee6f5bc5548a0f144da71404ab32 Mon Sep 17 00:00:00 2001 From: Jan Hosang Date: Wed, 23 Feb 2022 19:15:58 +0100 Subject: [PATCH 209/246] Codegolf --- lib/fuser_mqubit.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index e08a5b84..7f8d8cbf 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -954,12 +954,10 @@ class MultiQubitGateFuser final : public Fuser { if (ln != nullptr && rn != nullptr) { return R()(ln->val->parent->time, rn->val->parent->time); - } else if (ln != rn) { - return ln != nullptr || rn == nullptr; } else { // Comparator needs to be irreflexive to get a strict weak ordering. - return false; - } + return ln != nullptr && rn == nullptr; + } else { }); for (auto link : links) { From 1e370d1c1c4f8978b0e47ca4caff220fec9bc373 Mon Sep 17 00:00:00 2001 From: Jan Hosang Date: Wed, 23 Feb 2022 19:17:02 +0100 Subject: [PATCH 210/246] Update fuser_mqubit.h --- lib/fuser_mqubit.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index 7f8d8cbf..d3b2bee2 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -957,7 +957,7 @@ class MultiQubitGateFuser final : public Fuser { } else { // Comparator needs to be irreflexive to get a strict weak ordering. return ln != nullptr && rn == nullptr; - } else { + } }); for (auto link : links) { From 5ba900a818945a7b9bd6a4759564335c80ed12c8 Mon Sep 17 00:00:00 2001 From: Jan Hosang Date: Fri, 25 Feb 2022 17:08:08 +0100 Subject: [PATCH 211/246] Simplify and add comment. --- lib/fuser_mqubit.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index d3b2bee2..8ddb029e 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -955,8 +955,9 @@ class MultiQubitGateFuser final : public Fuser { if (ln != nullptr && rn != nullptr) { return R()(ln->val->parent->time, rn->val->parent->time); } else { - // Comparator needs to be irreflexive to get a strict weak ordering. - return ln != nullptr && rn == nullptr; + // nullptrs are larger than everything else and + // equivalent among each other. + return ln != nullptr; } }); From 5954623bbb528551a5ef1b19d70db3f038facfec Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 3 Mar 2022 08:48:06 -0800 Subject: [PATCH 212/246] Patch for internal tests --- lib/qtrajectory.h | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/qtrajectory.h b/lib/qtrajectory.h index 17cd83fb..1da6692f 100644 --- a/lib/qtrajectory.h +++ b/lib/qtrajectory.h @@ -16,6 +16,7 @@ #define QTRAJECTORY_H_ #include +#include #include #include #include From 93f394ba285472fdfd50e28c39b58cad50050074 Mon Sep 17 00:00:00 2001 From: Drew Risinger Date: Thu, 3 Mar 2022 11:16:49 -0500 Subject: [PATCH 213/246] cmake: allow using system pybind11 Defaults to pre-installed version of pybind11. The search was refactored to a cmake module. Will default to using the system pybind11, and fetch pybind11 via git if it is not installed --- pybind_interface/GetPybind11.cmake | 13 +++++++++++++ pybind_interface/avx2/CMakeLists.txt | 14 +------------- pybind_interface/avx512/CMakeLists.txt | 13 +------------ pybind_interface/basic/CMakeLists.txt | 13 +------------ pybind_interface/cuda/CMakeLists.txt | 14 +------------- pybind_interface/custatevec/CMakeLists.txt | 16 ++-------------- pybind_interface/decide/CMakeLists.txt | 13 +------------ pybind_interface/sse/CMakeLists.txt | 13 +------------ 8 files changed, 21 insertions(+), 88 deletions(-) create mode 100644 pybind_interface/GetPybind11.cmake diff --git a/pybind_interface/GetPybind11.cmake b/pybind_interface/GetPybind11.cmake new file mode 100644 index 00000000..ffeada3b --- /dev/null +++ b/pybind_interface/GetPybind11.cmake @@ -0,0 +1,13 @@ +include(FetchContent) + +FetchContent_Declare( + pybind11 + GIT_REPOSITORY https://github.com/pybind/pybind11 + GIT_TAG v2.2.4 +) +FetchContent_GetProperties(pybind11) +find_package(pybind11 CONFIG) +if((NOT pybind11_FOUND) AND (NOT pybind11_POPULATED)) # check first on system path, then attempt git fetch + FetchContent_Populate(pybind11) + add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) +endif() diff --git a/pybind_interface/avx2/CMakeLists.txt b/pybind_interface/avx2/CMakeLists.txt index 4382de95..eebba584 100644 --- a/pybind_interface/avx2/CMakeLists.txt +++ b/pybind_interface/avx2/CMakeLists.txt @@ -12,17 +12,5 @@ if(APPLE) include_directories("/usr/local/include" "/usr/local/opt/llvm/include") link_directories("/usr/local/lib" "/usr/local/opt/llvm/lib") endif() - -include(FetchContent) - -FetchContent_Declare( - pybind11 - GIT_REPOSITORY https://github.com/pybind/pybind11 - GIT_TAG v2.2.4 -) -FetchContent_GetProperties(pybind11) -if(NOT pybind11_POPULATED) - FetchContent_Populate(pybind11) - add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) -endif() +INCLUDE(../GetPybind11.cmake) pybind11_add_module(qsim_avx2 pybind_main_avx2.cpp) diff --git a/pybind_interface/avx512/CMakeLists.txt b/pybind_interface/avx512/CMakeLists.txt index 705d39db..86cfdfa8 100644 --- a/pybind_interface/avx512/CMakeLists.txt +++ b/pybind_interface/avx512/CMakeLists.txt @@ -14,16 +14,5 @@ if(APPLE) link_directories("/usr/local/lib" "/usr/local/opt/llvm/lib") endif() -include(FetchContent) - -FetchContent_Declare( - pybind11 - GIT_REPOSITORY https://github.com/pybind/pybind11 - GIT_TAG v2.2.4 -) -FetchContent_GetProperties(pybind11) -if(NOT pybind11_POPULATED) - FetchContent_Populate(pybind11) - add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) -endif() +INCLUDE(../GetPybind11.cmake) pybind11_add_module(qsim_avx512 pybind_main_avx512.cpp) diff --git a/pybind_interface/basic/CMakeLists.txt b/pybind_interface/basic/CMakeLists.txt index 9380f02d..35347211 100644 --- a/pybind_interface/basic/CMakeLists.txt +++ b/pybind_interface/basic/CMakeLists.txt @@ -14,16 +14,5 @@ if(APPLE) link_directories("/usr/local/lib" "/usr/local/opt/llvm/lib") endif() -include(FetchContent) - -FetchContent_Declare( - pybind11 - GIT_REPOSITORY https://github.com/pybind/pybind11 - GIT_TAG v2.2.4 -) -FetchContent_GetProperties(pybind11) -if(NOT pybind11_POPULATED) - FetchContent_Populate(pybind11) - add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) -endif() +INCLUDE(../GetPybind11.cmake) pybind11_add_module(qsim_basic pybind_main_basic.cpp) diff --git a/pybind_interface/cuda/CMakeLists.txt b/pybind_interface/cuda/CMakeLists.txt index d7f5b836..7da45dac 100644 --- a/pybind_interface/cuda/CMakeLists.txt +++ b/pybind_interface/cuda/CMakeLists.txt @@ -14,19 +14,7 @@ if(APPLE) link_directories("/usr/local/lib" "/usr/local/opt/llvm/lib") endif() -include(FetchContent) - -FetchContent_Declare( - pybind11 - GIT_REPOSITORY https://github.com/pybind/pybind11 - GIT_TAG v2.2.4 -) -FetchContent_GetProperties(pybind11) -if(NOT pybind11_POPULATED) - FetchContent_Populate(pybind11) - add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) -endif() - +INCLUDE(../GetPybind11.cmake) find_package(PythonLibs 3.6 REQUIRED) find_package(CUDA REQUIRED) diff --git a/pybind_interface/custatevec/CMakeLists.txt b/pybind_interface/custatevec/CMakeLists.txt index 912f531c..687ac2be 100644 --- a/pybind_interface/custatevec/CMakeLists.txt +++ b/pybind_interface/custatevec/CMakeLists.txt @@ -27,23 +27,11 @@ if(APPLE) link_directories("/usr/local/lib" "/usr/local/opt/llvm/lib") endif() -include(FetchContent) - -FetchContent_Declare( - pybind11 - GIT_REPOSITORY https://github.com/pybind/pybind11 - GIT_TAG v2.2.4 -) -FetchContent_GetProperties(pybind11) -if(NOT pybind11_POPULATED) - FetchContent_Populate(pybind11) - add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) -endif() - +INCLUDE(../GetPybind11.cmake) find_package(PythonLibs 3.6 REQUIRED) find_package(CUDA REQUIRED) -include_directories(${PYTHON_INCLUDE_DIRS} ${pybind11_SOURCE_DIR}/include) +include_directories(${pybind11_INCLUDE_DIRS}) include_directories($ENV{CUQUANTUM_DIR}/include) link_directories($ENV{CUQUANTUM_DIR}/lib $ENV{CUQUANTUM_DIR}/lib64) diff --git a/pybind_interface/decide/CMakeLists.txt b/pybind_interface/decide/CMakeLists.txt index 643ca9ad..0808ccf6 100644 --- a/pybind_interface/decide/CMakeLists.txt +++ b/pybind_interface/decide/CMakeLists.txt @@ -19,18 +19,7 @@ if(APPLE) link_directories("/usr/local/lib" "/usr/local/opt/llvm/lib") endif() -include(FetchContent) - -FetchContent_Declare( - pybind11 - GIT_REPOSITORY https://github.com/pybind/pybind11 - GIT_TAG v2.2.4 -) -FetchContent_GetProperties(pybind11) -if(NOT pybind11_POPULATED) - FetchContent_Populate(pybind11) - add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) -endif() +INCLUDE(../GetPybind11.cmake) if(has_nvcc STREQUAL "") pybind11_add_module(qsim_decide decide.cpp) diff --git a/pybind_interface/sse/CMakeLists.txt b/pybind_interface/sse/CMakeLists.txt index 6fa7201f..fe9b218e 100644 --- a/pybind_interface/sse/CMakeLists.txt +++ b/pybind_interface/sse/CMakeLists.txt @@ -14,16 +14,5 @@ if(APPLE) link_directories("/usr/local/lib" "/usr/local/opt/llvm/lib") endif() -include(FetchContent) - -FetchContent_Declare( - pybind11 - GIT_REPOSITORY https://github.com/pybind/pybind11 - GIT_TAG v2.2.4 -) -FetchContent_GetProperties(pybind11) -if(NOT pybind11_POPULATED) - FetchContent_Populate(pybind11) - add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) -endif() +INCLUDE(../GetPybind11.cmake) pybind11_add_module(qsim_sse pybind_main_sse.cpp) From 1c54e70acb260e6caad9bb9ad160762222d7230c Mon Sep 17 00:00:00 2001 From: Drew Risinger Date: Thu, 3 Mar 2022 12:23:49 -0500 Subject: [PATCH 214/246] cmake: set minimum pybind11 version --- pybind_interface/GetPybind11.cmake | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pybind_interface/GetPybind11.cmake b/pybind_interface/GetPybind11.cmake index ffeada3b..f93c2bf6 100644 --- a/pybind_interface/GetPybind11.cmake +++ b/pybind_interface/GetPybind11.cmake @@ -1,12 +1,13 @@ include(FetchContent) +set(MIN_PYBIND_VERSION "2.2.4") FetchContent_Declare( pybind11 GIT_REPOSITORY https://github.com/pybind/pybind11 - GIT_TAG v2.2.4 + GIT_TAG "v${MIN_PYBIND_VERSION}" ) FetchContent_GetProperties(pybind11) -find_package(pybind11 CONFIG) +find_package(pybind11 "${MIN_PYBIND_VERSION}" CONFIG) if((NOT pybind11_FOUND) AND (NOT pybind11_POPULATED)) # check first on system path, then attempt git fetch FetchContent_Populate(pybind11) add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) From 74620d996e827f63d27823388434bb1226960d8b Mon Sep 17 00:00:00 2001 From: Bernardo Meurer Date: Thu, 10 Mar 2022 16:59:45 -0800 Subject: [PATCH 215/246] Revert "Revert "refactor: split dev requirements out of main requirements.txt"" This reverts commit 05761621217604abb9f050485ee78a1a40b854ef. --- .github/workflows/python_format.yml | 6 ++---- .github/workflows/testing_wheels.yml | 1 + MANIFEST.in | 1 + dev-requirements.txt | 3 +++ docs/install_qsimcirq.md | 6 ++++++ requirements.txt | 12 ++---------- setup.py | 4 ++++ 7 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 dev-requirements.txt diff --git a/.github/workflows/python_format.yml b/.github/workflows/python_format.yml index adb5f56f..213abf2e 100644 --- a/.github/workflows/python_format.yml +++ b/.github/workflows/python_format.yml @@ -24,9 +24,7 @@ jobs: with: python-version: '3.7' architecture: 'x64' - - name: Install flynt - run: cat requirements.txt | grep flynt | xargs pip install - - name: Install black - run: cat requirements.txt | grep black | xargs pip install + - name: Install dev requirements + run: pip install -r dev-requirements.txt - name: Format run: check/format-incremental diff --git a/.github/workflows/testing_wheels.yml b/.github/workflows/testing_wheels.yml index eeb4d741..55dd237a 100644 --- a/.github/workflows/testing_wheels.yml +++ b/.github/workflows/testing_wheels.yml @@ -45,6 +45,7 @@ jobs: CIBW_REPAIR_WHEEL_COMMAND_MACOS: "" # due to package and module name conflict have to temporarily move it away to run tests CIBW_BEFORE_TEST: "mv {package}/qsimcirq /tmp" + CIBW_TEST_EXTRAS: "dev" CIBW_TEST_COMMAND: "pytest {package}/qsimcirq_tests/qsimcirq_test.py && mv /tmp/qsimcirq {package}" steps: - uses: actions/checkout@v2 diff --git a/MANIFEST.in b/MANIFEST.in index 2968589b..4b487267 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include requirements.txt +include dev-requirements.txt include CMakeLists.txt graft pybind_interface diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 00000000..4cb3afc0 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,3 @@ +black==20.8b1 +flynt~=0.60 +pytest diff --git a/docs/install_qsimcirq.md b/docs/install_qsimcirq.md index 9ae35d21..0a0f3182 100644 --- a/docs/install_qsimcirq.md +++ b/docs/install_qsimcirq.md @@ -17,6 +17,12 @@ Prerequisites are included in the [`requirements.txt`](https://github.com/quantumlib/qsim/blob/master/requirements.txt) file, and will be automatically installed along with qsimcirq. +If you'd like to develop qsimcirq, a separate set of dependencies are includes +in the +[`dev-requirements.txt`](https://github.com/quantumlib/qsim/blob/master/dev-requirements.txt) +file. You can install them with `pip3 install -r dev-requirements.txt` or +`pip3 install qsimcirq[dev]`. + ## Linux installation We provide `qsimcirq` Python wheels on 64-bit `x86` architectures with `Python 3.{6,7,8,9}`. diff --git a/requirements.txt b/requirements.txt index 01d20cd5..0a95d64c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,5 @@ -# Runtime requirements for the python 3 version of cirq. - +absl-py cirq-core numpy~=1.16 -typing_extensions -absl-py - -# Build and test requirements - -black==20.8b1 -flynt~=0.60 pybind11 -pytest +typing_extensions diff --git a/setup.py b/setup.py index 121b4dad..a7b002c3 100644 --- a/setup.py +++ b/setup.py @@ -78,6 +78,7 @@ def build_extension(self, ext): requirements = open("requirements.txt").readlines() +dev_requirements = open("dev-requirements.txt").readlines() description = "Schrödinger and Schrödinger-Feynman simulators for quantum circuits." @@ -95,6 +96,9 @@ def build_extension(self, ext): author_email="devabathini92@gmail.com", python_requires=">=3.3.0", install_requires=requirements, + extras_require={ + "dev": dev_requirements, + }, license="Apache 2", description=description, long_description=long_description, From 9ebb5c909bb6f39ba0a350b9c3c34f558bb20e66 Mon Sep 17 00:00:00 2001 From: Bernardo Meurer Date: Thu, 10 Mar 2022 17:00:05 -0800 Subject: [PATCH 216/246] Revert "Revert #469" This reverts commit 51ba016984d63f2594a57bbbc1b368f28e746f8d. --- .github/workflows/cirq_compatibility.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cirq_compatibility.yml b/.github/workflows/cirq_compatibility.yml index 04e13559..f4fd7473 100644 --- a/.github/workflows/cirq_compatibility.yml +++ b/.github/workflows/cirq_compatibility.yml @@ -18,5 +18,7 @@ jobs: run: pip3 install -U cirq --pre - name: Install qsim requirements run: pip3 install -r requirements.txt + - name: Install test requirements + run: pip3 install -r dev-requirements.txt - name: Run python tests run: make run-py-tests From 8503958570b2744c4f01f0dddfae4951b25d3b10 Mon Sep 17 00:00:00 2001 From: Bernardo Meurer Date: Thu, 10 Mar 2022 17:05:23 -0800 Subject: [PATCH 217/246] ci(release_wheels): install dev requirements in CIBW_TEST_EXTRAS --- .github/workflows/release_wheels.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release_wheels.yml b/.github/workflows/release_wheels.yml index 1550d20e..abf5c7a1 100644 --- a/.github/workflows/release_wheels.yml +++ b/.github/workflows/release_wheels.yml @@ -39,7 +39,8 @@ jobs: CIBW_BEFORE_BUILD_MACOS: "brew install libomp" CIBW_REPAIR_WHEEL_COMMAND_MACOS: "" # due to package and module name conflict have to temporarily move it away to run tests - CIBW_BEFORE_TEST: "mv {package}/qsimcirq /tmp" + CIBW_BEFORE_TEST: mv {package}/qsimcirq /tmp + CIBW_TEST_EXTRAS: "dev" CIBW_TEST_COMMAND: "pytest {package}/qsimcirq_tests/qsimcirq_test.py && mv /tmp/qsimcirq {package}" steps: - uses: actions/checkout@v2 From 33f6b4ec099dde918b6c942105554883b54624e0 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 14 Mar 2022 12:52:19 -0700 Subject: [PATCH 218/246] Update version to 0.12.1 --- qsimcirq/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq/_version.py b/qsimcirq/_version.py index 4016bd7b..1315a776 100644 --- a/qsimcirq/_version.py +++ b/qsimcirq/_version.py @@ -1,3 +1,3 @@ """The version number defined here is read automatically in setup.py.""" -__version__ = "0.12.0" +__version__ = "0.12.1" From 823d316458210a3fd18b7108bf95270d3db9896a Mon Sep 17 00:00:00 2001 From: Bernardo Meurer Date: Tue, 15 Mar 2022 09:24:06 -0700 Subject: [PATCH 219/246] refactor(dev-requirements): use stable black --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 4cb3afc0..9c4745bb 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,3 +1,3 @@ -black==20.8b1 +black~=22.1.0 flynt~=0.60 pytest From e4e26cae41d683bc00da2040e5b3cd9c7f5080b2 Mon Sep 17 00:00:00 2001 From: Bernardo Meurer Date: Tue, 15 Mar 2022 09:24:15 -0700 Subject: [PATCH 220/246] style: format with black 22.1.0 --- dev-requirements.txt | 2 +- qsimcirq/qsim_simulator.py | 8 ++++---- qsimcirq_tests/qsimcirq_test.py | 13 +++++-------- setup.py | 2 +- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 9c4745bb..858ac6bc 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,3 +1,3 @@ -black~=22.1.0 +black~=22.0 flynt~=0.60 pytest diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 935cfd75..017dcbfb 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -250,7 +250,7 @@ def __init__( def get_seed(self): # Limit seed size to 32-bit integer for C++ conversion. - return self._prng.randint(2 ** 31 - 1) + return self._prng.randint(2**31 - 1) def _run( self, @@ -540,7 +540,7 @@ def simulate_sweep( if initial_state.dtype != np.complex64: raise TypeError(f"initial_state vector must have dtype np.complex64.") input_vector = initial_state.view(np.float32) - if len(input_vector) != 2 ** num_qubits * 2: + if len(input_vector) != 2**num_qubits * 2: raise ValueError( f"initial_state vector size must match number of qubits." f"Expected: {2**num_qubits * 2} Received: {len(input_vector)}" @@ -672,7 +672,7 @@ def simulate_expectation_values_sweep( if initial_state.dtype != np.complex64: raise TypeError(f"initial_state vector must have dtype np.complex64.") input_vector = initial_state.view(np.float32) - if len(input_vector) != 2 ** num_qubits * 2: + if len(input_vector) != 2**num_qubits * 2: raise ValueError( f"initial_state vector size must match number of qubits." f"Expected: {2**num_qubits * 2} Received: {len(input_vector)}" @@ -807,7 +807,7 @@ def simulate_moment_expectation_values( if initial_state.dtype != np.complex64: raise TypeError(f"initial_state vector must have dtype np.complex64.") input_vector = initial_state.view(np.float32) - if len(input_vector) != 2 ** num_qubits * 2: + if len(input_vector) != 2**num_qubits * 2: raise ValueError( f"initial_state vector size must match number of qubits." f"Expected: {2**num_qubits * 2} Received: {len(input_vector)}" diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index ac855a87..0af3ed0d 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -286,14 +286,11 @@ def test_iterable_qubit_order(): ) qsim_simulator = qsimcirq.QSimSimulator() - assert ( - qsim_simulator.compute_amplitudes( - circuit, - bitstrings=[0b00, 0b01], - qubit_order=reversed([q1, q0]), - ) - == qsim_simulator.compute_amplitudes(circuit, bitstrings=[0b00, 0b01]) - ) + assert qsim_simulator.compute_amplitudes( + circuit, + bitstrings=[0b00, 0b01], + qubit_order=reversed([q1, q0]), + ) == qsim_simulator.compute_amplitudes(circuit, bitstrings=[0b00, 0b01]) assert qsim_simulator.simulate( circuit, qubit_order=reversed([q1, q0]) diff --git a/setup.py b/setup.py index a7b002c3..10c7aa15 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ def build_extension(self, ext): cmake_args += [ "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}".format(cfg.upper(), extdir) ] - if sys.maxsize > 2 ** 32: + if sys.maxsize > 2**32: cmake_args += ["-A", "x64"] build_args += ["--", "/m"] else: From 1bec1eda261beecd531c93cbcc6d2c0d15735269 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 16 Mar 2022 13:59:26 -0700 Subject: [PATCH 221/246] Workaround Centos 8 EOL --- jupyter/Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jupyter/Dockerfile b/jupyter/Dockerfile index 2019390c..43e12544 100644 --- a/jupyter/Dockerfile +++ b/jupyter/Dockerfile @@ -3,6 +3,11 @@ FROM centos:8 USER root # Install baseline +RUN pushd /etc/yum.repos.d/ +RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* +RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* +RUN popd + RUN yum -y update && \ yum install -y epel-release && \ yum group install -y "Development Tools" && \ From a9691e3710ff3661183a4d7ed4ff1d6873dbbaeb Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 18 Mar 2022 09:10:19 -0700 Subject: [PATCH 222/246] Document EOL changes. --- jupyter/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jupyter/Dockerfile b/jupyter/Dockerfile index 43e12544..a1ddb8ff 100644 --- a/jupyter/Dockerfile +++ b/jupyter/Dockerfile @@ -1,13 +1,15 @@ # Base OS FROM centos:8 USER root -# Install baseline +# Centos 8 has reach end of life: https://www.centos.org/centos-linux-eol/ +# Configuration must be loaded from the vault. RUN pushd /etc/yum.repos.d/ RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* RUN popd +# Install baseline RUN yum -y update && \ yum install -y epel-release && \ yum group install -y "Development Tools" && \ From 5c7ea933cde433b685edd777f05b1aea5cb73cae Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 18 Mar 2022 10:34:27 -0700 Subject: [PATCH 223/246] Contain pushd-popd in single RUN --- jupyter/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jupyter/Dockerfile b/jupyter/Dockerfile index a1ddb8ff..35610cd9 100644 --- a/jupyter/Dockerfile +++ b/jupyter/Dockerfile @@ -4,10 +4,10 @@ USER root # Centos 8 has reach end of life: https://www.centos.org/centos-linux-eol/ # Configuration must be loaded from the vault. -RUN pushd /etc/yum.repos.d/ -RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* -RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* -RUN popd +RUN pushd /etc/yum.repos.d/ && \ + sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* && \ + sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* && \ + popd # Install baseline RUN yum -y update && \ From 9d4c647fab0c16aee57b5afbfb7a780854bc081c Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 28 Mar 2022 11:45:36 -0700 Subject: [PATCH 224/246] Remove deprecated elements for Cirq v0.14 --- qsimcirq/qsim_circuit.py | 10 +++------- qsimcirq/qsim_simulator.py | 34 ++++++++++++++++----------------- qsimcirq/qsimh_simulator.py | 2 +- qsimcirq_tests/qsimcirq_test.py | 11 ++++++----- 4 files changed, 26 insertions(+), 31 deletions(-) diff --git a/qsimcirq/qsim_circuit.py b/qsimcirq/qsim_circuit.py index 912f8fd7..21a2094e 100644 --- a/qsimcirq/qsim_circuit.py +++ b/qsimcirq/qsim_circuit.py @@ -252,18 +252,17 @@ class QSimCircuit(cirq.Circuit): def __init__( self, cirq_circuit: cirq.Circuit, - device: cirq.devices = cirq.devices.UNCONSTRAINED_DEVICE, allow_decomposition: bool = False, ): if allow_decomposition: - super().__init__([], device=device) + super().__init__() for moment in cirq_circuit: for op in moment: # This should call decompose on the gates self.append(op) else: - super().__init__(cirq_circuit, device=device) + super().__init__(cirq_circuit) def __eq__(self, other): if not isinstance(other, QSimCircuit): @@ -274,10 +273,7 @@ def __eq__(self, other): def _resolve_parameters_( self, param_resolver: cirq.study.ParamResolver, recursive: bool = True ): - return QSimCircuit( - cirq.resolve_parameters(super(), param_resolver, recursive), - device=self.device, - ) + return QSimCircuit(cirq.resolve_parameters(super(), param_resolver, recursive)) def translate_cirq_to_qsim( self, qubit_order: cirq.ops.QubitOrderOrList = cirq.ops.QubitOrder.DEFAULT diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 017dcbfb..ab234f1c 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -305,7 +305,6 @@ def _sample_measure_results( self.noise.noisy_moments(program, sorted(all_qubits)) if self.noise is not cirq.NO_NOISE else program, - device=program.device, ) # Compute indices of measured qubits @@ -324,14 +323,15 @@ def _sample_measure_results( cirq.MeasurementGate ) ] - measured_qubits = [] # type: List[cirq.Qid] - bounds = {} # type: Dict[str, Tuple] - meas_ops = {} # type: Dict[str, cirq.GateOperation] + measured_qubits: List[cirq.Qid] = [] + bounds: Dict[str, Tuple] = {} + meas_ops: Dict[str, List[cirq.GateOperation]] = {} current_index = 0 for op in measurement_ops: gate = op.gate key = cirq.measurement_key_name(gate) - meas_ops[key] = op + meas_ops.setdefault(key, []) + meas_ops[key].append(op) if key in bounds: raise ValueError(f"Duplicate MeasurementGate with key {key}") bounds[key] = (current_index, current_index + len(op.qubits)) @@ -345,7 +345,7 @@ def _sample_measure_results( results = {} for key, bound in bounds.items(): results[key] = np.ndarray( - shape=(repetitions, bound[1] - bound[0]), dtype=int + shape=(repetitions, len(meas_ops[key]), bound[1] - bound[0]), dtype=int ) noisy = _needs_trajectories(program) @@ -374,11 +374,12 @@ def _sample_measure_results( ] ) - for key, op in meas_ops.items(): - meas_indices = [qubit_map[qubit] for qubit in op.qubits] - invert_mask = op.gate.full_invert_mask() - # Apply invert mask to re-ordered results - results[key] = full_results[:, meas_indices] ^ invert_mask + for key, oplist in meas_ops.items(): + for i, op in enumerate(oplist): + meas_indices = [qubit_map[qubit] for qubit in op.qubits] + invert_mask = op.gate.full_invert_mask() + # Apply invert mask to re-ordered results + results[key][:, i, :] = full_results[:, meas_indices] ^ invert_mask else: if noisy: @@ -396,7 +397,7 @@ def _sample_measure_results( measurements = np.empty( shape=( repetitions, - sum(cirq.num_qubits(op) for op in meas_ops.values()), + sum(cirq.num_qubits(op) for oplist in meas_ops.values() for op in oplist), ), dtype=int, ) @@ -405,8 +406,9 @@ def _sample_measure_results( measurements[i] = sampler_fn(options) for key, (start, end) in bounds.items(): - invert_mask = meas_ops[key].gate.full_invert_mask() - results[key] = measurements[:, start:end] ^ invert_mask + for i, op in enumerate(meas_ops[key]): + invert_mask = op.gate.full_invert_mask() + results[key][:, i, :] = measurements[:, start:end] ^ invert_mask return results @@ -442,7 +444,6 @@ def compute_amplitudes_sweep( self.noise.noisy_moments(program, sorted(all_qubits)) if self.noise is not cirq.NO_NOISE else program, - device=program.device, ) # qsim numbers qubits in reverse order from cirq @@ -525,7 +526,6 @@ def simulate_sweep( self.noise.noisy_moments(program, sorted(all_qubits)) if self.noise is not cirq.NO_NOISE else program, - device=program.device, ) options = {} @@ -661,7 +661,6 @@ def simulate_expectation_values_sweep( self.noise.noisy_moments(program, sorted(all_qubits)) if self.noise is not cirq.NO_NOISE else program, - device=program.device, ) options = {} @@ -796,7 +795,6 @@ def simulate_moment_expectation_values( self.noise.noisy_moments(program, sorted(all_qubits)) if self.noise is not cirq.NO_NOISE else program, - device=program.device, ) options = {} diff --git a/qsimcirq/qsimh_simulator.py b/qsimcirq/qsimh_simulator.py index 6a060c42..3077a935 100644 --- a/qsimcirq/qsimh_simulator.py +++ b/qsimcirq/qsimh_simulator.py @@ -48,7 +48,7 @@ def compute_amplitudes_sweep( ) -> Sequence[Sequence[complex]]: if not isinstance(program, qsimc.QSimCircuit): - program = qsimc.QSimCircuit(program, device=program.device) + program = qsimc.QSimCircuit(program) n_qubits = len(program.all_qubits()) # qsim numbers qubits in reverse order from cirq diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 0af3ed0d..f14fc44f 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -271,10 +271,11 @@ def test_invalid_params(): x, y = sympy.Symbol("x"), sympy.Symbol("y") circuit = cirq.Circuit(cirq.X(q0) ** x, cirq.H(q0) ** y) prs = [{x: np.int64(0), y: np.int64(1)}, {x: np.int64(1), y: "z"}] + sweep = cirq.ListSweep(prs) qsim_simulator = qsimcirq.QSimSimulator() with pytest.raises(ValueError, match="Parameters must be numeric"): - _ = qsim_simulator.simulate_sweep(circuit, params=prs) + _ = qsim_simulator.simulate_sweep(circuit, params=sweep) def test_iterable_qubit_order(): @@ -1154,10 +1155,10 @@ def test_cirq_qsim_simulate_random_unitary(mode: str): qubits=[q0, q1], n_moments=8, op_density=0.99, random_state=iter ) - cirq.ConvertToCzAndSingleGates().optimize_circuit( - random_circuit - ) # cannot work with params - cirq.ExpandComposite().optimize_circuit(random_circuit) + random_circuit = cirq.optimize_for_target_gateset( + random_circuit, gateset=cirq.CZTargetGateset() + ) + random_circuit = cirq.expand_composite(random_circuit) if mode == "noisy": random_circuit.append(NoiseTrigger().on(q0)) From 0c855d7ca50719dd7197ea7d682bd8cfcb6f5c6d Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 28 Mar 2022 11:49:38 -0700 Subject: [PATCH 225/246] Format --- qsimcirq/qsim_simulator.py | 14 +++++++++----- qsimcirq_tests/qsimcirq_test.py | 13 ++++++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index ab234f1c..868bff3b 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -250,7 +250,7 @@ def __init__( def get_seed(self): # Limit seed size to 32-bit integer for C++ conversion. - return self._prng.randint(2**31 - 1) + return self._prng.randint(2 ** 31 - 1) def _run( self, @@ -397,7 +397,11 @@ def _sample_measure_results( measurements = np.empty( shape=( repetitions, - sum(cirq.num_qubits(op) for oplist in meas_ops.values() for op in oplist), + sum( + cirq.num_qubits(op) + for oplist in meas_ops.values() + for op in oplist + ), ), dtype=int, ) @@ -540,7 +544,7 @@ def simulate_sweep( if initial_state.dtype != np.complex64: raise TypeError(f"initial_state vector must have dtype np.complex64.") input_vector = initial_state.view(np.float32) - if len(input_vector) != 2**num_qubits * 2: + if len(input_vector) != 2 ** num_qubits * 2: raise ValueError( f"initial_state vector size must match number of qubits." f"Expected: {2**num_qubits * 2} Received: {len(input_vector)}" @@ -671,7 +675,7 @@ def simulate_expectation_values_sweep( if initial_state.dtype != np.complex64: raise TypeError(f"initial_state vector must have dtype np.complex64.") input_vector = initial_state.view(np.float32) - if len(input_vector) != 2**num_qubits * 2: + if len(input_vector) != 2 ** num_qubits * 2: raise ValueError( f"initial_state vector size must match number of qubits." f"Expected: {2**num_qubits * 2} Received: {len(input_vector)}" @@ -805,7 +809,7 @@ def simulate_moment_expectation_values( if initial_state.dtype != np.complex64: raise TypeError(f"initial_state vector must have dtype np.complex64.") input_vector = initial_state.view(np.float32) - if len(input_vector) != 2**num_qubits * 2: + if len(input_vector) != 2 ** num_qubits * 2: raise ValueError( f"initial_state vector size must match number of qubits." f"Expected: {2**num_qubits * 2} Received: {len(input_vector)}" diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index f14fc44f..9b7cf416 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -287,11 +287,14 @@ def test_iterable_qubit_order(): ) qsim_simulator = qsimcirq.QSimSimulator() - assert qsim_simulator.compute_amplitudes( - circuit, - bitstrings=[0b00, 0b01], - qubit_order=reversed([q1, q0]), - ) == qsim_simulator.compute_amplitudes(circuit, bitstrings=[0b00, 0b01]) + assert ( + qsim_simulator.compute_amplitudes( + circuit, + bitstrings=[0b00, 0b01], + qubit_order=reversed([q1, q0]), + ) + == qsim_simulator.compute_amplitudes(circuit, bitstrings=[0b00, 0b01]) + ) assert qsim_simulator.simulate( circuit, qubit_order=reversed([q1, q0]) From 8ad72e972d1c80b5d545c817f706d595496fecfe Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 28 Mar 2022 11:52:03 -0700 Subject: [PATCH 226/246] Pin click version --- dev-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-requirements.txt b/dev-requirements.txt index 858ac6bc..25489459 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,3 +1,4 @@ black~=22.0 +click<=8.0.4 flynt~=0.60 pytest From 6df09448df81f4901e549e15ccd4aa71608857f1 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 28 Mar 2022 14:44:55 -0700 Subject: [PATCH 227/246] Update `black` version, unpin `click` --- dev-requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 25489459..2d2638bc 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,4 +1,3 @@ -black~=22.0 -click<=8.0.4 +black~=22.3.0 flynt~=0.60 pytest From dd5b2d0d16f349bd37dd7284d191818b35675772 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 30 Mar 2022 11:48:05 -0700 Subject: [PATCH 228/246] Format --- qsimcirq/qsim_simulator.py | 8 ++++---- qsimcirq_tests/qsimcirq_test.py | 13 +++++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 868bff3b..ee34d67f 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -250,7 +250,7 @@ def __init__( def get_seed(self): # Limit seed size to 32-bit integer for C++ conversion. - return self._prng.randint(2 ** 31 - 1) + return self._prng.randint(2**31 - 1) def _run( self, @@ -544,7 +544,7 @@ def simulate_sweep( if initial_state.dtype != np.complex64: raise TypeError(f"initial_state vector must have dtype np.complex64.") input_vector = initial_state.view(np.float32) - if len(input_vector) != 2 ** num_qubits * 2: + if len(input_vector) != 2**num_qubits * 2: raise ValueError( f"initial_state vector size must match number of qubits." f"Expected: {2**num_qubits * 2} Received: {len(input_vector)}" @@ -675,7 +675,7 @@ def simulate_expectation_values_sweep( if initial_state.dtype != np.complex64: raise TypeError(f"initial_state vector must have dtype np.complex64.") input_vector = initial_state.view(np.float32) - if len(input_vector) != 2 ** num_qubits * 2: + if len(input_vector) != 2**num_qubits * 2: raise ValueError( f"initial_state vector size must match number of qubits." f"Expected: {2**num_qubits * 2} Received: {len(input_vector)}" @@ -809,7 +809,7 @@ def simulate_moment_expectation_values( if initial_state.dtype != np.complex64: raise TypeError(f"initial_state vector must have dtype np.complex64.") input_vector = initial_state.view(np.float32) - if len(input_vector) != 2 ** num_qubits * 2: + if len(input_vector) != 2**num_qubits * 2: raise ValueError( f"initial_state vector size must match number of qubits." f"Expected: {2**num_qubits * 2} Received: {len(input_vector)}" diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 9b7cf416..f14fc44f 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -287,14 +287,11 @@ def test_iterable_qubit_order(): ) qsim_simulator = qsimcirq.QSimSimulator() - assert ( - qsim_simulator.compute_amplitudes( - circuit, - bitstrings=[0b00, 0b01], - qubit_order=reversed([q1, q0]), - ) - == qsim_simulator.compute_amplitudes(circuit, bitstrings=[0b00, 0b01]) - ) + assert qsim_simulator.compute_amplitudes( + circuit, + bitstrings=[0b00, 0b01], + qubit_order=reversed([q1, q0]), + ) == qsim_simulator.compute_amplitudes(circuit, bitstrings=[0b00, 0b01]) assert qsim_simulator.simulate( circuit, qubit_order=reversed([q1, q0]) From 2328827af9e6b90ba3fdae9fad229b9f12ff7d6e Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Fri, 1 Apr 2022 17:12:08 +0200 Subject: [PATCH 229/246] Switch to cuStateVec v1.0.0 or higher. --- docs/cirq_interface.md | 5 +++-- docs/tutorials/gcp_gpu.md | 3 ++- lib/simulator_custatevec.h | 10 +++++----- lib/statespace_custatevec.h | 10 +++++----- tests/statespace_custatevec_test.cu | 1 - 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/cirq_interface.md b/docs/cirq_interface.md index 321c985c..24149708 100644 --- a/docs/cirq_interface.md +++ b/docs/cirq_interface.md @@ -179,8 +179,9 @@ and run on a device with available NVIDIA GPUs. Compilation for GPU follows the same steps outlined in the [Compiling qsimcirq](./cirq_interface.md#compiling-qsimcirq) section. -To compile with the NVIDIA cuStateVec library, set the environmment variable -`CUQUANTUM_DIR` to the path to the cuStateVec library. +To compile with the NVIDIA cuStateVec library (v1.0.0 or higher is required), +set the environmment variable `CUQUANTUM_DIR` to the path to the cuStateVec +library. `QSimOptions` provides five parameters to configure GPU execution. `use_gpu` is required to enable GPU execution: diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index e352cfe9..cf57e48d 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -179,7 +179,8 @@ After a moment, you should see a result that looks similar to the following. If you have the [NVIDIA cuQuantum SDK](https://developer.nvidia.com/cuquantum-sdk) installed (instructions are provided -[here](https://docs.nvidia.com/cuda/cuquantum/custatevec/html/getting_started.html#installation-and-compilation)), +[here](https://docs.nvidia.com/cuda/cuquantum/custatevec/html/getting_started.html#installation-and-compilation), +cuStateVec v1.0.0 or higher is required), you can use it with this tutorial. Before building qsim in step 5, set the `CUQUANTUM_DIR` environment variable from the command line: diff --git a/lib/simulator_custatevec.h b/lib/simulator_custatevec.h index 05e91e16..df01d974 100644 --- a/lib/simulator_custatevec.h +++ b/lib/simulator_custatevec.h @@ -65,7 +65,7 @@ class SimulatorCuStateVec final { ErrorCheck(custatevecApplyMatrix( handle_, state.get(), kStateType, state.num_qubits(), matrix, kMatrixType, kMatrixLayout, 0, - (int32_t*) qs.data(), qs.size(), nullptr, 0, nullptr, + (int32_t*) qs.data(), qs.size(), nullptr, nullptr, 0, kComputeType, workspace_, workspace_size)); } @@ -95,7 +95,7 @@ class SimulatorCuStateVec final { handle_, state.get(), kStateType, state.num_qubits(), matrix, kMatrixType, kMatrixLayout, 0, (int32_t*) qs.data(), qs.size(), - (int32_t*) cqs.data(), cqs.size(), control_bits.data(), + (int32_t*) cqs.data(), control_bits.data(), cqs.size(), kComputeType, workspace_, workspace_size)); } @@ -116,7 +116,7 @@ class SimulatorCuStateVec final { cuDoubleComplex eval; - ErrorCheck(custatevecExpectation( + ErrorCheck(custatevecComputeExpectation( handle_, state.get(), kStateType, state.num_qubits(), &eval, kExpectType, nullptr, matrix, kMatrixType, kMatrixLayout, (int32_t*) qs.data(), qs.size(), @@ -138,7 +138,7 @@ class SimulatorCuStateVec final { const fp_type* matrix) const { size_t size; - ErrorCheck(custatevecApplyMatrix_bufferSize( + ErrorCheck(custatevecApplyMatrixGetWorkspaceSize( handle_, kStateType, num_qubits, matrix, kMatrixType, kMatrixLayout, 0, num_targets, num_controls, kComputeType, &size)); @@ -150,7 +150,7 @@ class SimulatorCuStateVec final { unsigned num_qubits, unsigned num_targets, const fp_type* matrix) const { size_t size; - ErrorCheck(custatevecExpectation_bufferSize( + ErrorCheck(custatevecComputeExpectationGetWorkspaceSize( handle_, kStateType, num_qubits, matrix, kMatrixType, kMatrixLayout, num_targets, kComputeType, &size)); diff --git a/lib/statespace_custatevec.h b/lib/statespace_custatevec.h index dd2b4599..12d8db55 100644 --- a/lib/statespace_custatevec.h +++ b/lib/statespace_custatevec.h @@ -247,15 +247,15 @@ class StateSpaceCuStateVec : size_t workspace_size; custatevecSamplerDescriptor_t sampler; - ErrorCheck(custatevecSampler_create( + ErrorCheck(custatevecSamplerCreate( custatevec_handle_, state.get(), kStateType, state.num_qubits(), &sampler, num_samples, &workspace_size)); AllocWorkSpace(workspace_size); - ErrorCheck(custatevecSampler_preprocess( - custatevec_handle_, &sampler, workspace_, workspace_size)); + ErrorCheck(custatevecSamplerPreprocess( + custatevec_handle_, sampler, workspace_, workspace_size)); std::vector bitstrings0(num_samples); std::vector bitordering; @@ -265,8 +265,8 @@ class StateSpaceCuStateVec : bitordering.push_back(i); } - ErrorCheck(custatevecSampler_sample( - custatevec_handle_, &sampler, bitstrings0.data(), + ErrorCheck(custatevecSamplerSample( + custatevec_handle_, sampler, bitstrings0.data(), bitordering.data(), state.num_qubits(), rs.data(), num_samples, CUSTATEVEC_SAMPLER_OUTPUT_RANDNUM_ORDER)); diff --git a/tests/statespace_custatevec_test.cu b/tests/statespace_custatevec_test.cu index 833bbee0..841be8ee 100644 --- a/tests/statespace_custatevec_test.cu +++ b/tests/statespace_custatevec_test.cu @@ -96,7 +96,6 @@ TYPED_TEST(StateSpaceCuStateVecTest, MeasurementLarge) { } TYPED_TEST(StateSpaceCuStateVecTest, Collapse) { -// This test fails. TestCollapse(qsim::Factory()); } From c44a31089cffa818561499796f0c7069acf84cd4 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 8 Apr 2022 10:19:28 -0700 Subject: [PATCH 230/246] Reduce isinstance calls --- qsimcirq/qsim_circuit.py | 254 ++++++++++++++++++++++----------------- 1 file changed, 142 insertions(+), 112 deletions(-) diff --git a/qsimcirq/qsim_circuit.py b/qsimcirq/qsim_circuit.py index 912f8fd7..689df881 100644 --- a/qsimcirq/qsim_circuit.py +++ b/qsimcirq/qsim_circuit.py @@ -33,104 +33,140 @@ "theta", ] - -def _cirq_gate_kind(gate: cirq.ops.Gate): - if isinstance(gate, cirq.ops.ControlledGate): - return _cirq_gate_kind(gate.sub_gate) - if isinstance(gate, cirq.ops.identity.IdentityGate): - # Identity gates will decompose to no-ops. - pass - if isinstance(gate, cirq.ops.XPowGate): - # cirq.rx also uses this path. - if gate.exponent == 1 and gate.global_shift == 0: - return qsim.kX - return qsim.kXPowGate - if isinstance(gate, cirq.ops.YPowGate): - # cirq.ry also uses this path. - if gate.exponent == 1 and gate.global_shift == 0: - return qsim.kY - return qsim.kYPowGate - if isinstance(gate, cirq.ops.ZPowGate): - # cirq.rz also uses this path. - if gate.global_shift == 0: - if gate.exponent == 1: - return qsim.kZ - if gate.exponent == 0.5: - return qsim.kS - if gate.exponent == 0.25: - return qsim.kT - return qsim.kZPowGate - if isinstance(gate, cirq.ops.HPowGate): - if gate.exponent == 1 and gate.global_shift == 0: - return qsim.kH - return qsim.kHPowGate - if isinstance(gate, cirq.ops.CZPowGate): - if gate.exponent == 1 and gate.global_shift == 0: - return qsim.kCZ - return qsim.kCZPowGate - if isinstance(gate, cirq.ops.CXPowGate): - if gate.exponent == 1 and gate.global_shift == 0: - return qsim.kCX - return qsim.kCXPowGate - if isinstance(gate, cirq.ops.PhasedXPowGate): - return qsim.kPhasedXPowGate - if isinstance(gate, cirq.ops.PhasedXZGate): - return qsim.kPhasedXZGate - if isinstance(gate, cirq.ops.XXPowGate): - if gate.exponent == 1 and gate.global_shift == 0: - return qsim.kXX - return qsim.kXXPowGate - if isinstance(gate, cirq.ops.YYPowGate): - if gate.exponent == 1 and gate.global_shift == 0: - return qsim.kYY - return qsim.kYYPowGate - if isinstance(gate, cirq.ops.ZZPowGate): - if gate.exponent == 1 and gate.global_shift == 0: - return qsim.kZZ - return qsim.kZZPowGate - if isinstance(gate, cirq.ops.SwapPowGate): - if gate.exponent == 1 and gate.global_shift == 0: - return qsim.kSWAP - return qsim.kSwapPowGate - if isinstance(gate, cirq.ops.ISwapPowGate): - # cirq.riswap also uses this path. - if gate.exponent == 1 and gate.global_shift == 0: - return qsim.kISWAP - return qsim.kISwapPowGate - if isinstance(gate, cirq.ops.PhasedISwapPowGate): - # cirq.givens also uses this path. - return qsim.kPhasedISwapPowGate - if isinstance(gate, cirq.ops.FSimGate): - return qsim.kFSimGate - if isinstance(gate, cirq.ops.TwoQubitDiagonalGate): - return qsim.kTwoQubitDiagonalGate - if isinstance(gate, cirq.ops.ThreeQubitDiagonalGate): - return qsim.kThreeQubitDiagonalGate - if isinstance(gate, cirq.ops.CCZPowGate): - if gate.exponent == 1 and gate.global_shift == 0: - return qsim.kCCZ - return qsim.kCCZPowGate - if isinstance(gate, cirq.ops.CCXPowGate): - if gate.exponent == 1 and gate.global_shift == 0: - return qsim.kCCX - return qsim.kCCXPowGate - if isinstance(gate, cirq.ops.CSwapGate): - return qsim.kCSwapGate - if isinstance(gate, cirq.ops.MatrixGate): - if gate.num_qubits() <= 6: - return qsim.kMatrixGate - raise NotImplementedError( - f"Received matrix on {gate.num_qubits()} qubits; " - + "only up to 6-qubit gates are supported." - ) - if isinstance(gate, cirq.ops.MeasurementGate): - # needed to inherit SimulatesSamples in sims - return qsim.kMeasurement +def _translate_ControlledGate(gate: cirq.ControlledGate): + return _cirq_gate_kind(gate.sub_gate) +def _translate_IdentityGate(gate: cirq.IdentityGate): + # Identity gates will decompose to no-ops. + pass +def _translate_XPowGate(gate: cirq.XPowGate): + # cirq.rx also uses this path. + if gate.exponent == 1 and gate.global_shift == 0: + return qsim.kX + return qsim.kXPowGate +def _translate_YPowGate(gate: cirq.YPowGate): + # cirq.ry also uses this path. + if gate.exponent == 1 and gate.global_shift == 0: + return qsim.kY + return qsim.kYPowGate +def _translate_ZPowGate(gate: cirq.ZPowGate): + # cirq.rz also uses this path. + if gate.global_shift == 0: + if gate.exponent == 1: + return qsim.kZ + if gate.exponent == 0.5: + return qsim.kS + if gate.exponent == 0.25: + return qsim.kT + return qsim.kZPowGate +def _translate_HPowGate(gate: cirq.HPowGate): + if gate.exponent == 1 and gate.global_shift == 0: + return qsim.kH + return qsim.kHPowGate +def _translate(gate: cirq.CZPowGate): + if gate.exponent == 1 and gate.global_shift == 0: + return qsim.kCZ + return qsim.kCZPowGate +def _translate_CXPowGate(gate: cirq.CXPowGate): + if gate.exponent == 1 and gate.global_shift == 0: + return qsim.kCX + return qsim.kCXPowGate +def _translate_PhasedXPowGate(gate: cirq.PhasedXPowGate): + return qsim.kPhasedXPowGate +def _translate_PhasedXZGate(gate: cirq.PhasedXZGate): + return qsim.kPhasedXZGate +def _translate_XXPowGate(gate: cirq.XXPowGate): + if gate.exponent == 1 and gate.global_shift == 0: + return qsim.kXX + return qsim.kXXPowGate +def _translate_YYPowGate(gate: cirq.YYPowGate): + if gate.exponent == 1 and gate.global_shift == 0: + return qsim.kYY + return qsim.kYYPowGate +def _translate_ZZPowGate(gate: cirq.ZZPowGate): + if gate.exponent == 1 and gate.global_shift == 0: + return qsim.kZZ + return qsim.kZZPowGate +def _translate_SwapPowGate(gate: cirq.SwapPowGate): + if gate.exponent == 1 and gate.global_shift == 0: + return qsim.kSWAP + return qsim.kSwapPowGate +def _translate_ISwapPowGate(gate: cirq.ISwapPowGate): + # cirq.riswap also uses this path. + if gate.exponent == 1 and gate.global_shift == 0: + return qsim.kISWAP + return qsim.kISwapPowGate +def _translate_PhasedISwapPowGate(gate: cirq.PhasedISwapPowGate): + # cirq.givens also uses this path. + return qsim.kPhasedISwapPowGate +def _translate_FSimGate(gate: cirq.FSimGate): + return qsim.kFSimGate +def _translate_TwoQubitDiagonalGate(gate: cirq.TwoQubitDiagonalGate): + return qsim.kTwoQubitDiagonalGate +def _translate_ThreeQubitDiagonalGate(gate: cirq.ThreeQubitDiagonalGate): + return qsim.kThreeQubitDiagonalGate +def _translate_CCZPowGate(gate: cirq.CCZPowGate): + if gate.exponent == 1 and gate.global_shift == 0: + return qsim.kCCZ + return qsim.kCCZPowGate +def _translate_CCXPowGate(gate: cirq.CCXPowGate): + if gate.exponent == 1 and gate.global_shift == 0: + return qsim.kCCX + return qsim.kCCXPowGate +def _translate_CSwapGate(gate: cirq.CSwapGate): + return qsim.kCSwapGate +def _translate_MatrixGate(gate: cirq.MatrixGate): + if gate.num_qubits() <= 6: + return qsim.kMatrixGate + raise NotImplementedError( + f"Received matrix on {gate.num_qubits()} qubits; " + + "only up to 6-qubit gates are supported." + ) +def _translate_MeasurementGate(gate: cirq.MeasurementGate): + # needed to inherit SimulatesSamples in sims + return qsim.kMeasurement + + +TYPE_TRANSLATOR = { + cirq.ControlledGate: _translate_ControlledGate, + cirq.IdentityGate: _translate_IdentityGate, + cirq.XPowGate: _translate_XPowGate, + cirq.YPowGate: _translate_YPowGate, + cirq.ZPowGate: _translate_ZPowGate, + cirq.HPowGate: _translate_HPowGate, + cirq.CXPowGate: _translate_CXPowGate, + cirq.PhasedXPowGate: _translate_PhasedXPowGate, + cirq.PhasedXZGate: _translate_PhasedXZGate, + cirq.XXPowGate: _translate_XXPowGate, + cirq.YYPowGate: _translate_YYPowGate, + cirq.ZZPowGate: _translate_ZZPowGate, + cirq.SwapPowGate: _translate_SwapPowGate, + cirq.ISwapPowGate: _translate_ISwapPowGate, + cirq.PhasedISwapPowGate: _translate_PhasedISwapPowGate, + cirq.FSimGate: _translate_FSimGate, + cirq.TwoQubitDiagonalGate: _translate_TwoQubitDiagonalGate, + cirq.ThreeQubitDiagonalGate: _translate_ThreeQubitDiagonalGate, + cirq.CCZPowGate: _translate_CCZPowGate, + cirq.CCXPowGate: _translate_CCXPowGate, + cirq.CSwapGate: _translate_CSwapGate, + cirq.MatrixGate: _translate_MatrixGate, + cirq.MeasurementGate: _translate_MeasurementGate, +} + + +def _cirq_gate_kind(gate: cirq.Gate): + for gate_type in type(gate).mro(): + translator = TYPE_TRANSLATOR.get(gate_type, None) + if translator is not None: + return translator(gate) # Unrecognized gates will be decomposed. return None -def _control_details(gate: cirq.ops.ControlledGate, qubits): +def _has_cirq_gate_kind(op: cirq.Operation): + return any(t in TYPE_TRANSLATOR for t in type(op.gate).mro()) + + +def _control_details(gate: cirq.ControlledGate, qubits): control_qubits = [] control_values = [] # TODO: support qudit control @@ -169,7 +205,7 @@ def add_op_to_opstring( if len(qsim_op.qubits) != 1: raise ValueError(f"OpString ops should have 1 qubit; got {len(qsim_op.qubits)}") - is_controlled = isinstance(qsim_gate, cirq.ops.ControlledGate) + is_controlled = isinstance(qsim_gate, cirq.ControlledGate) if is_controlled: raise ValueError(f"OpString ops should not be controlled.") @@ -189,7 +225,7 @@ def add_op_to_circuit( qubits = [qubit_to_index_dict[q] for q in qsim_op.qubits] qsim_qubits = qubits - is_controlled = isinstance(qsim_gate, cirq.ops.ControlledGate) + is_controlled = isinstance(qsim_gate, cirq.ControlledGate) if is_controlled: control_qubits, control_values = _control_details(qsim_gate, qubits) if control_qubits is None: @@ -280,7 +316,7 @@ def _resolve_parameters_( ) def translate_cirq_to_qsim( - self, qubit_order: cirq.ops.QubitOrderOrList = cirq.ops.QubitOrder.DEFAULT + self, qubit_order: cirq.QubitOrderOrList = cirq.QubitOrder.DEFAULT ) -> qsim.Circuit: """ Translates this Cirq circuit to the qsim representation. @@ -290,7 +326,7 @@ def translate_cirq_to_qsim( """ qsim_circuit = qsim.Circuit() - ordered_qubits = cirq.ops.QubitOrder.as_qubit_order(qubit_order).order_for( + ordered_qubits = cirq.QubitOrder.as_qubit_order(qubit_order).order_for( self.all_qubits() ) qsim_circuit.num_qubits = len(ordered_qubits) @@ -298,15 +334,12 @@ def translate_cirq_to_qsim( # qsim numbers qubits in reverse order from cirq ordered_qubits = list(reversed(ordered_qubits)) - def has_qsim_kind(op: cirq.ops.GateOperation): - return _cirq_gate_kind(op.gate) != None - - def to_matrix(op: cirq.ops.GateOperation): + def to_matrix(op: cirq.GateOperation): mat = cirq.unitary(op.gate, None) if mat is None: return NotImplemented - return cirq.ops.MatrixGate(mat).on(*op.qubits) + return cirq.MatrixGate(mat).on(*op.qubits) qubit_to_index_dict = {q: i for i, q in enumerate(ordered_qubits)} time_offset = 0 @@ -314,7 +347,7 @@ def to_matrix(op: cirq.ops.GateOperation): moment_indices = [] for moment in self: ops_by_gate = [ - cirq.decompose(op, fallback_decomposer=to_matrix, keep=has_qsim_kind) + cirq.decompose(op, fallback_decomposer=to_matrix, keep=_has_cirq_gate_kind) for op in moment ] moment_length = max((len(gate_ops) for gate_ops in ops_by_gate), default=0) @@ -334,7 +367,7 @@ def to_matrix(op: cirq.ops.GateOperation): return qsim_circuit, moment_indices def translate_cirq_to_qtrajectory( - self, qubit_order: cirq.ops.QubitOrderOrList = cirq.ops.QubitOrder.DEFAULT + self, qubit_order: cirq.QubitOrderOrList = cirq.QubitOrder.DEFAULT ) -> qsim.NoisyCircuit: """ Translates this noisy Cirq circuit to the qsim representation. @@ -343,7 +376,7 @@ def translate_cirq_to_qtrajectory( gate indices) """ qsim_ncircuit = qsim.NoisyCircuit() - ordered_qubits = cirq.ops.QubitOrder.as_qubit_order(qubit_order).order_for( + ordered_qubits = cirq.QubitOrder.as_qubit_order(qubit_order).order_for( self.all_qubits() ) @@ -352,15 +385,12 @@ def translate_cirq_to_qtrajectory( qsim_ncircuit.num_qubits = len(ordered_qubits) - def has_qsim_kind(op: cirq.ops.GateOperation): - return _cirq_gate_kind(op.gate) != None - - def to_matrix(op: cirq.ops.GateOperation): + def to_matrix(op: cirq.GateOperation): mat = cirq.unitary(op.gate, None) if mat is None: return NotImplemented - return cirq.ops.MatrixGate(mat).on(*op.qubits) + return cirq.MatrixGate(mat).on(*op.qubits) qubit_to_index_dict = {q: i for i, q in enumerate(ordered_qubits)} time_offset = 0 @@ -375,7 +405,7 @@ def to_matrix(op: cirq.ops.GateOperation): for qsim_op in moment: if cirq.has_unitary(qsim_op) or cirq.is_measurement(qsim_op): oplist = cirq.decompose( - qsim_op, fallback_decomposer=to_matrix, keep=has_qsim_kind + qsim_op, fallback_decomposer=to_matrix, keep=_has_cirq_gate_kind ) ops_by_gate.append(oplist) moment_length = max(moment_length, len(oplist)) From e4bcadc6a05e9fd14c0ca551ef323b87cdb9df0a Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 8 Apr 2022 12:30:06 -0700 Subject: [PATCH 231/246] format --- qsimcirq/qsim_circuit.py | 51 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/qsimcirq/qsim_circuit.py b/qsimcirq/qsim_circuit.py index 689df881..67324a75 100644 --- a/qsimcirq/qsim_circuit.py +++ b/qsimcirq/qsim_circuit.py @@ -33,21 +33,30 @@ "theta", ] + def _translate_ControlledGate(gate: cirq.ControlledGate): return _cirq_gate_kind(gate.sub_gate) + + def _translate_IdentityGate(gate: cirq.IdentityGate): # Identity gates will decompose to no-ops. pass + + def _translate_XPowGate(gate: cirq.XPowGate): # cirq.rx also uses this path. if gate.exponent == 1 and gate.global_shift == 0: return qsim.kX return qsim.kXPowGate + + def _translate_YPowGate(gate: cirq.YPowGate): # cirq.ry also uses this path. if gate.exponent == 1 and gate.global_shift == 0: return qsim.kY return qsim.kYPowGate + + def _translate_ZPowGate(gate: cirq.ZPowGate): # cirq.rz also uses this path. if gate.global_shift == 0: @@ -58,62 +67,98 @@ def _translate_ZPowGate(gate: cirq.ZPowGate): if gate.exponent == 0.25: return qsim.kT return qsim.kZPowGate + + def _translate_HPowGate(gate: cirq.HPowGate): if gate.exponent == 1 and gate.global_shift == 0: return qsim.kH return qsim.kHPowGate + + def _translate(gate: cirq.CZPowGate): if gate.exponent == 1 and gate.global_shift == 0: return qsim.kCZ return qsim.kCZPowGate + + def _translate_CXPowGate(gate: cirq.CXPowGate): if gate.exponent == 1 and gate.global_shift == 0: return qsim.kCX return qsim.kCXPowGate + + def _translate_PhasedXPowGate(gate: cirq.PhasedXPowGate): return qsim.kPhasedXPowGate + + def _translate_PhasedXZGate(gate: cirq.PhasedXZGate): return qsim.kPhasedXZGate + + def _translate_XXPowGate(gate: cirq.XXPowGate): if gate.exponent == 1 and gate.global_shift == 0: return qsim.kXX return qsim.kXXPowGate + + def _translate_YYPowGate(gate: cirq.YYPowGate): if gate.exponent == 1 and gate.global_shift == 0: return qsim.kYY return qsim.kYYPowGate + + def _translate_ZZPowGate(gate: cirq.ZZPowGate): if gate.exponent == 1 and gate.global_shift == 0: return qsim.kZZ return qsim.kZZPowGate + + def _translate_SwapPowGate(gate: cirq.SwapPowGate): if gate.exponent == 1 and gate.global_shift == 0: return qsim.kSWAP return qsim.kSwapPowGate + + def _translate_ISwapPowGate(gate: cirq.ISwapPowGate): # cirq.riswap also uses this path. if gate.exponent == 1 and gate.global_shift == 0: return qsim.kISWAP return qsim.kISwapPowGate + + def _translate_PhasedISwapPowGate(gate: cirq.PhasedISwapPowGate): # cirq.givens also uses this path. return qsim.kPhasedISwapPowGate + + def _translate_FSimGate(gate: cirq.FSimGate): return qsim.kFSimGate + + def _translate_TwoQubitDiagonalGate(gate: cirq.TwoQubitDiagonalGate): return qsim.kTwoQubitDiagonalGate + + def _translate_ThreeQubitDiagonalGate(gate: cirq.ThreeQubitDiagonalGate): return qsim.kThreeQubitDiagonalGate + + def _translate_CCZPowGate(gate: cirq.CCZPowGate): if gate.exponent == 1 and gate.global_shift == 0: return qsim.kCCZ return qsim.kCCZPowGate + + def _translate_CCXPowGate(gate: cirq.CCXPowGate): if gate.exponent == 1 and gate.global_shift == 0: return qsim.kCCX return qsim.kCCXPowGate + + def _translate_CSwapGate(gate: cirq.CSwapGate): return qsim.kCSwapGate + + def _translate_MatrixGate(gate: cirq.MatrixGate): if gate.num_qubits() <= 6: return qsim.kMatrixGate @@ -121,6 +166,8 @@ def _translate_MatrixGate(gate: cirq.MatrixGate): f"Received matrix on {gate.num_qubits()} qubits; " + "only up to 6-qubit gates are supported." ) + + def _translate_MeasurementGate(gate: cirq.MeasurementGate): # needed to inherit SimulatesSamples in sims return qsim.kMeasurement @@ -347,7 +394,9 @@ def to_matrix(op: cirq.GateOperation): moment_indices = [] for moment in self: ops_by_gate = [ - cirq.decompose(op, fallback_decomposer=to_matrix, keep=_has_cirq_gate_kind) + cirq.decompose( + op, fallback_decomposer=to_matrix, keep=_has_cirq_gate_kind + ) for op in moment ] moment_length = max((len(gate_ops) for gate_ops in ops_by_gate), default=0) From f6d281de923309883aadb3066a4ee35dfb2706b2 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 8 Apr 2022 12:50:47 -0700 Subject: [PATCH 232/246] Repair tests --- qsimcirq/qsim_circuit.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/qsimcirq/qsim_circuit.py b/qsimcirq/qsim_circuit.py index 67324a75..df9572a0 100644 --- a/qsimcirq/qsim_circuit.py +++ b/qsimcirq/qsim_circuit.py @@ -38,11 +38,6 @@ def _translate_ControlledGate(gate: cirq.ControlledGate): return _cirq_gate_kind(gate.sub_gate) -def _translate_IdentityGate(gate: cirq.IdentityGate): - # Identity gates will decompose to no-ops. - pass - - def _translate_XPowGate(gate: cirq.XPowGate): # cirq.rx also uses this path. if gate.exponent == 1 and gate.global_shift == 0: @@ -175,7 +170,6 @@ def _translate_MeasurementGate(gate: cirq.MeasurementGate): TYPE_TRANSLATOR = { cirq.ControlledGate: _translate_ControlledGate, - cirq.IdentityGate: _translate_IdentityGate, cirq.XPowGate: _translate_XPowGate, cirq.YPowGate: _translate_YPowGate, cirq.ZPowGate: _translate_ZPowGate, @@ -210,6 +204,8 @@ def _cirq_gate_kind(gate: cirq.Gate): def _has_cirq_gate_kind(op: cirq.Operation): + if isinstance(op, cirq.ControlledOperation): + return _has_cirq_gate_kind(op.sub_operation) return any(t in TYPE_TRANSLATOR for t in type(op.gate).mro()) From 04065804ccd1950a685c755ada6e50b3f39fd111 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 11 Apr 2022 11:46:53 -0700 Subject: [PATCH 233/246] Include CZ --- qsimcirq/qsim_circuit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qsimcirq/qsim_circuit.py b/qsimcirq/qsim_circuit.py index df9572a0..3b23d945 100644 --- a/qsimcirq/qsim_circuit.py +++ b/qsimcirq/qsim_circuit.py @@ -70,7 +70,7 @@ def _translate_HPowGate(gate: cirq.HPowGate): return qsim.kHPowGate -def _translate(gate: cirq.CZPowGate): +def _translate_CZPowGate(gate: cirq.CZPowGate): if gate.exponent == 1 and gate.global_shift == 0: return qsim.kCZ return qsim.kCZPowGate @@ -174,6 +174,7 @@ def _translate_MeasurementGate(gate: cirq.MeasurementGate): cirq.YPowGate: _translate_YPowGate, cirq.ZPowGate: _translate_ZPowGate, cirq.HPowGate: _translate_HPowGate, + cirq.CZPowGate: _translate_CZPowGate, cirq.CXPowGate: _translate_CXPowGate, cirq.PhasedXPowGate: _translate_PhasedXPowGate, cirq.PhasedXZGate: _translate_PhasedXZGate, From 53025df032e1b9296713f3d8f7866e9936ed1af7 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 22 Apr 2022 09:51:16 -0700 Subject: [PATCH 234/246] Update to dev version 2022-04-22 --- qsimcirq/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq/_version.py b/qsimcirq/_version.py index 1315a776..a2c2c9c2 100644 --- a/qsimcirq/_version.py +++ b/qsimcirq/_version.py @@ -1,3 +1,3 @@ """The version number defined here is read automatically in setup.py.""" -__version__ = "0.12.1" +__version__ = "0.12.2.dev20220422" From 68df71712ff607b9e0f7eff293923773cf07bda9 Mon Sep 17 00:00:00 2001 From: Matthew Neeley Date: Tue, 3 May 2022 13:49:17 -0700 Subject: [PATCH 235/246] Remove usage of deprecated `cirq.SingleQubitGate` --- qsimcirq_tests/qsimcirq_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index f14fc44f..6e516676 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -19,7 +19,7 @@ import qsimcirq -class NoiseTrigger(cirq.SingleQubitGate): +class NoiseTrigger(cirq.Gate): """A no-op gate with no _unitary_ method defined. Appending this gate to a circuit will force it to use qtrajectory, but the @@ -29,6 +29,9 @@ class NoiseTrigger(cirq.SingleQubitGate): # def _mixture_(self): # return ((1.0, np.asarray([1, 0, 0, 1])),) + def _num_qubits_(self) -> int: + return 1 + def _kraus_(self): return (np.asarray([1, 0, 0, 1]),) From a5944a8cec8bcc2c6a19b76165ac55ccc833b1e3 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 13 May 2022 12:52:26 -0700 Subject: [PATCH 236/246] Remove mac-arm build from release --- .github/workflows/release_wheels.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/release_wheels.yml b/.github/workflows/release_wheels.yml index abf5c7a1..33550cb7 100644 --- a/.github/workflows/release_wheels.yml +++ b/.github/workflows/release_wheels.yml @@ -15,12 +15,7 @@ jobs: - os: macos-10.15 name: mac cibw: - build: "cp36* cp37* cp38*" - - os: macos-10.15 - name: mac-arm - cibw: - arch: universal2 - build: "cp39*" + build: "cp36* cp37* cp38* cp39*" - os: ubuntu-20.04 name: manylinux2014 cibw: From 963efd8241c80eec796f8bef3f88419b883bd138 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 13 May 2022 12:53:11 -0700 Subject: [PATCH 237/246] Remove mac-arm from wheel tests --- .github/workflows/testing_wheels.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/testing_wheels.yml b/.github/workflows/testing_wheels.yml index 55dd237a..916f728b 100644 --- a/.github/workflows/testing_wheels.yml +++ b/.github/workflows/testing_wheels.yml @@ -20,12 +20,7 @@ jobs: - os: macos-10.15 name: mac cibw: - build: "cp36* cp37* cp38*" - - os: macos-10.15 - name: mac-arm - cibw: - arch: universal2 - build: "cp39*" + build: "cp36* cp37* cp38* cp39*" - os: ubuntu-20.04 name: manylinux2014 cibw: From ec74f20a9072ddeb7f31175de30b3cf7fcdeca99 Mon Sep 17 00:00:00 2001 From: Matthew Neeley Date: Fri, 27 May 2022 16:11:15 -0700 Subject: [PATCH 238/246] Add support for repeated keys in QSimSimulator --- qsimcirq/qsim_simulator.py | 33 +++++++++++++++++++-------------- qsimcirq_tests/qsimcirq_test.py | 16 ++++++++++++++++ 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index ee34d67f..3353f959 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -323,8 +323,8 @@ def _sample_measure_results( cirq.MeasurementGate ) ] - measured_qubits: List[cirq.Qid] = [] - bounds: Dict[str, Tuple] = {} + num_qubits_by_key: Dict[str, int] = {} + bounds: Dict[str, Tuple[int, int]] = {} meas_ops: Dict[str, List[cirq.GateOperation]] = {} current_index = 0 for op in measurement_ops: @@ -332,21 +332,26 @@ def _sample_measure_results( key = cirq.measurement_key_name(gate) meas_ops.setdefault(key, []) meas_ops[key].append(op) - if key in bounds: - raise ValueError(f"Duplicate MeasurementGate with key {key}") - bounds[key] = (current_index, current_index + len(op.qubits)) - measured_qubits.extend(op.qubits) - current_index += len(op.qubits) + n = len(op.qubits) + if key in num_qubits_by_key: + if n != num_qubits_by_key[key]: + raise ValueError( + f'repeated key {key!r} with different numbers of qubits: ' + f'{num_qubits_by_key[key]} != {n}' + ) + else: + num_qubits_by_key[key] = n + if key not in bounds: + bounds[key] = (current_index, current_index + n) + current_index += n # Set qsim options - options = {} - options.update(self.qsim_options) + options = {**self.qsim_options} - results = {} - for key, bound in bounds.items(): - results[key] = np.ndarray( - shape=(repetitions, len(meas_ops[key]), bound[1] - bound[0]), dtype=int - ) + results = { + key: np.ndarray(shape=(repetitions, len(meas_ops[key]), n), dtype=int) + for key, n in num_qubits_by_key.items() + } noisy = _needs_trajectories(program) if not noisy and program.are_all_measurements_terminal() and repetitions > 1: diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 6e516676..e0d84bad 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -57,6 +57,22 @@ def test_empty_moment(mode: str): assert result.final_state_vector.shape == (4,) +def test_repeated_keys(): + q = cirq.LineQubit(0) + circuit = cirq.Circuit( + cirq.measure(q, key='m'), + cirq.X(q), + cirq.measure(q, key='m'), + cirq.X(q), + cirq.measure(q, key='m'), + ) + result = qsimcirq.QSimSimulator().simulate(circuit, repetitions=10) + assert result.records['m'] == (10, 3, 1) + assert np.all(result.records['m'][:, 0, :] == 0) + assert np.all(result.records['m'][:, 1, :] == 1) + assert np.all(result.records['m'][:, 2, :] == 0) + + def test_cirq_too_big_gate(): # Pick qubits. a, b, c, d, e, f, g = [ From e3068c49a80506c6fd72c00e74e148e7868a5e3b Mon Sep 17 00:00:00 2001 From: Matthew Neeley Date: Fri, 27 May 2022 16:23:48 -0700 Subject: [PATCH 239/246] Format --- qsimcirq/qsim_simulator.py | 4 ++-- qsimcirq_tests/qsimcirq_test.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 3353f959..d6b3c1e5 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -336,8 +336,8 @@ def _sample_measure_results( if key in num_qubits_by_key: if n != num_qubits_by_key[key]: raise ValueError( - f'repeated key {key!r} with different numbers of qubits: ' - f'{num_qubits_by_key[key]} != {n}' + f"repeated key {key!r} with different numbers of qubits: " + f"{num_qubits_by_key[key]} != {n}" ) else: num_qubits_by_key[key] = n diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index e0d84bad..5320865b 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -60,17 +60,17 @@ def test_empty_moment(mode: str): def test_repeated_keys(): q = cirq.LineQubit(0) circuit = cirq.Circuit( - cirq.measure(q, key='m'), + cirq.measure(q, key="m"), cirq.X(q), - cirq.measure(q, key='m'), + cirq.measure(q, key="m"), cirq.X(q), - cirq.measure(q, key='m'), + cirq.measure(q, key="m"), ) result = qsimcirq.QSimSimulator().simulate(circuit, repetitions=10) - assert result.records['m'] == (10, 3, 1) - assert np.all(result.records['m'][:, 0, :] == 0) - assert np.all(result.records['m'][:, 1, :] == 1) - assert np.all(result.records['m'][:, 2, :] == 0) + assert result.records["m"] == (10, 3, 1) + assert np.all(result.records["m"][:, 0, :] == 0) + assert np.all(result.records["m"][:, 1, :] == 1) + assert np.all(result.records["m"][:, 2, :] == 0) def test_cirq_too_big_gate(): From 6d0397993ec36b77519166d46d2c32dc12705b9b Mon Sep 17 00:00:00 2001 From: Matthew Neeley Date: Fri, 27 May 2022 16:25:43 -0700 Subject: [PATCH 240/246] s/simulate/run/ --- qsimcirq_tests/qsimcirq_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 5320865b..82651df7 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -66,7 +66,7 @@ def test_repeated_keys(): cirq.X(q), cirq.measure(q, key="m"), ) - result = qsimcirq.QSimSimulator().simulate(circuit, repetitions=10) + result = qsimcirq.QSimSimulator().run(circuit, repetitions=10) assert result.records["m"] == (10, 3, 1) assert np.all(result.records["m"][:, 0, :] == 0) assert np.all(result.records["m"][:, 1, :] == 1) From 99d4ce9e8e19749e6c19214557ecde3d9aa26b9f Mon Sep 17 00:00:00 2001 From: Matthew Neeley Date: Tue, 31 May 2022 09:26:30 -0700 Subject: [PATCH 241/246] Store bounds for each instance of a measurement key --- qsimcirq/qsim_simulator.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index d6b3c1e5..06df3f8f 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -324,7 +324,7 @@ def _sample_measure_results( ) ] num_qubits_by_key: Dict[str, int] = {} - bounds: Dict[str, Tuple[int, int]] = {} + bounds: List[Tuple[str, int, int]] = [] meas_ops: Dict[str, List[cirq.GateOperation]] = {} current_index = 0 for op in measurement_ops: @@ -341,8 +341,7 @@ def _sample_measure_results( ) else: num_qubits_by_key[key] = n - if key not in bounds: - bounds[key] = (current_index, current_index + n) + bounds.append((key, current_index, current_index + n)) current_index += n # Set qsim options @@ -414,7 +413,7 @@ def _sample_measure_results( options["s"] = self.get_seed() measurements[i] = sampler_fn(options) - for key, (start, end) in bounds.items(): + for key, start, end in bounds: for i, op in enumerate(meas_ops[key]): invert_mask = op.gate.full_invert_mask() results[key][:, i, :] = measurements[:, start:end] ^ invert_mask From 4b828c38ab78c02fc889a16a0f1f64a7d3db06bd Mon Sep 17 00:00:00 2001 From: Matthew Neeley Date: Tue, 31 May 2022 10:03:32 -0700 Subject: [PATCH 242/246] Fix bounds calculation --- qsimcirq/qsim_simulator.py | 38 +++++++++++++-------------------- qsimcirq_tests/qsimcirq_test.py | 2 +- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 06df3f8f..78a85e92 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -313,10 +313,13 @@ def _sample_measure_results( qubit_map = {qubit: index for index, qubit in enumerate(ordered_qubits)} - # Computes - # - the list of qubits to be measured - # - the start (inclusive) and end (exclusive) indices of each measurement - # - a mapping from measurement key to measurement gate + # Compute: + # - number of qubits for each measurement key. + # - measurement ops for each measurement key. + # - info about each measurement, including the key, instance (for repeated keys), + # start (inclusive) and end (exclusive) indices in the qsim output, and + # invert mask. + # - total number of measured bits. measurement_ops = [ op for _, op, _ in program.findall_operations_with_gate_type( @@ -324,13 +327,14 @@ def _sample_measure_results( ) ] num_qubits_by_key: Dict[str, int] = {} - bounds: List[Tuple[str, int, int]] = [] meas_ops: Dict[str, List[cirq.GateOperation]] = {} - current_index = 0 + info: List[Tuple[str, int, Tuple[bool, ...], int, int]] = [] + num_bits = 0 for op in measurement_ops: gate = op.gate key = cirq.measurement_key_name(gate) meas_ops.setdefault(key, []) + i = len(meas_ops[key]) meas_ops[key].append(op) n = len(op.qubits) if key in num_qubits_by_key: @@ -341,8 +345,8 @@ def _sample_measure_results( ) else: num_qubits_by_key[key] = n - bounds.append((key, current_index, current_index + n)) - current_index += n + info.append((key, i, gate.full_invert_mask(), num_bits, num_bits + n)) + num_bits += n # Set qsim options options = {**self.qsim_options} @@ -398,25 +402,13 @@ def _sample_measure_results( translator_fn_name, cirq.QubitOrder.DEFAULT, ) - measurements = np.empty( - shape=( - repetitions, - sum( - cirq.num_qubits(op) - for oplist in meas_ops.values() - for op in oplist - ), - ), - dtype=int, - ) + measurements = np.empty(shape=(repetitions, num_bits), dtype=int) for i in range(repetitions): options["s"] = self.get_seed() measurements[i] = sampler_fn(options) - for key, start, end in bounds: - for i, op in enumerate(meas_ops[key]): - invert_mask = op.gate.full_invert_mask() - results[key][:, i, :] = measurements[:, start:end] ^ invert_mask + for key, i, invert_mask, start, end in info: + results[key][:, i, :] = measurements[:, start:end] ^ invert_mask return results diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index 82651df7..ec8c4a23 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -67,7 +67,7 @@ def test_repeated_keys(): cirq.measure(q, key="m"), ) result = qsimcirq.QSimSimulator().run(circuit, repetitions=10) - assert result.records["m"] == (10, 3, 1) + assert result.records["m"].shape == (10, 3, 1) assert np.all(result.records["m"][:, 0, :] == 0) assert np.all(result.records["m"][:, 1, :] == 1) assert np.all(result.records["m"][:, 2, :] == 0) From 8cdd0bfbc8915f41595150b210cdb489265d299d Mon Sep 17 00:00:00 2001 From: Matthew Neeley Date: Tue, 31 May 2022 11:21:15 -0700 Subject: [PATCH 243/246] Remove unused imports --- qsimcirq/qsim_simulator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 78a85e92..b15bee0a 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -14,8 +14,7 @@ from collections import deque from dataclasses import dataclass -from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple, Union -from xml.etree.ElementPath import ops +from typing import Any, Dict, List, Optional, Sequence, Tuple, Union import cirq From 4ca3c883c6103f2c6d26982b845218940d01c5f4 Mon Sep 17 00:00:00 2001 From: Matthew Neeley Date: Tue, 31 May 2022 12:38:45 -0700 Subject: [PATCH 244/246] Update testing instructions in README (#532) Review: @95-martin-orion --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b5e5f6f9..767e4c2c 100644 --- a/README.md +++ b/README.md @@ -85,12 +85,14 @@ located in [tests](https://github.com/quantumlib/qsim/tree/master/tests). Python tests use pytest, and are located in [qsimcirq_tests](https://github.com/quantumlib/qsim/tree/master/qsimcirq_tests). -To build and run all tests, navigate to the test directory and run: +To build and run all tests, run: ``` -make run-all +make run-tests ``` This will compile all test binaries to files with `.x` extensions, and run each -test in series. Testing will stop early if a test fails. +test in series. Testing will stop early if a test fails. It will also run tests +of the `qsimcirq` python interface. To run C++ or python tests only, run +`make run-cxx-tests` or `make run-py-tests`, respectively. To clean up generated test files, run `make clean` from the test directory. @@ -125,4 +127,4 @@ An equivalent BibTex format reference is below for all the versions: doi = {10.5281/zenodo.4023103}, url = {https://doi.org/10.5281/zenodo.4023103} } -``` \ No newline at end of file +``` From ece373cb718bd7bae04607031a298c299d73f519 Mon Sep 17 00:00:00 2001 From: Matthew Neeley Date: Thu, 2 Jun 2022 14:38:54 -0700 Subject: [PATCH 245/246] Fixes from review --- qsimcirq/qsim_simulator.py | 41 +++++++++++++++++++++++++------ qsimcirq_tests/qsimcirq_test.py | 43 +++++++++++++++++++++++++++------ 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index b15bee0a..5835c574 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -167,6 +167,25 @@ def as_dict(self): } +@dataclass +class MeasInfo: + """Info about each measure operation in the circuit being simulated. + + Attributes: + key: The measurement key. + idx: The "instance" of a possibly-repeated measurement key. + invert_mask: True for any measurement bits that should be inverted. + start: Start index in qsim's output array for this measurement. + end: End index (non-inclusive) in qsim's output array. + """ + + key: str + idx: int + invert_mask: Tuple[bool, ...] + start: int + end: int + + class QSimSimulator( cirq.SimulatesSamples, cirq.SimulatesAmplitudes, @@ -315,9 +334,7 @@ def _sample_measure_results( # Compute: # - number of qubits for each measurement key. # - measurement ops for each measurement key. - # - info about each measurement, including the key, instance (for repeated keys), - # start (inclusive) and end (exclusive) indices in the qsim output, and - # invert mask. + # - measurement info for each measurement. # - total number of measured bits. measurement_ops = [ op @@ -327,7 +344,7 @@ def _sample_measure_results( ] num_qubits_by_key: Dict[str, int] = {} meas_ops: Dict[str, List[cirq.GateOperation]] = {} - info: List[Tuple[str, int, Tuple[bool, ...], int, int]] = [] + meas_infos: List[MeasInfo] = [] num_bits = 0 for op in measurement_ops: gate = op.gate @@ -344,7 +361,15 @@ def _sample_measure_results( ) else: num_qubits_by_key[key] = n - info.append((key, i, gate.full_invert_mask(), num_bits, num_bits + n)) + meas_infos.append( + MeasInfo( + key=key, + idx=i, + invert_mask=gate.full_invert_mask(), + start=num_bits, + end=num_bits + n, + ) + ) num_bits += n # Set qsim options @@ -406,8 +431,10 @@ def _sample_measure_results( options["s"] = self.get_seed() measurements[i] = sampler_fn(options) - for key, i, invert_mask, start, end in info: - results[key][:, i, :] = measurements[:, start:end] ^ invert_mask + for m in meas_infos: + results[m.key][:, m.idx, :] = ( + measurements[:, m.start : m.end] ^ m.invert_mask + ) return results diff --git a/qsimcirq_tests/qsimcirq_test.py b/qsimcirq_tests/qsimcirq_test.py index ec8c4a23..ce439f29 100644 --- a/qsimcirq_tests/qsimcirq_test.py +++ b/qsimcirq_tests/qsimcirq_test.py @@ -58,19 +58,46 @@ def test_empty_moment(mode: str): def test_repeated_keys(): - q = cirq.LineQubit(0) + q0, q1 = cirq.LineQubit.range(2) circuit = cirq.Circuit( - cirq.measure(q, key="m"), - cirq.X(q), - cirq.measure(q, key="m"), - cirq.X(q), - cirq.measure(q, key="m"), + cirq.Moment(cirq.measure(q0, key="m")), + cirq.Moment(cirq.X(q1)), + cirq.Moment(cirq.measure(q1, key="m")), + cirq.Moment(cirq.X(q0)), + cirq.Moment(cirq.measure(q0, key="m")), + cirq.Moment(cirq.X(q1)), + cirq.Moment(cirq.measure(q1, key="m")), ) result = qsimcirq.QSimSimulator().run(circuit, repetitions=10) - assert result.records["m"].shape == (10, 3, 1) + assert result.records["m"].shape == (10, 4, 1) assert np.all(result.records["m"][:, 0, :] == 0) assert np.all(result.records["m"][:, 1, :] == 1) - assert np.all(result.records["m"][:, 2, :] == 0) + assert np.all(result.records["m"][:, 2, :] == 1) + assert np.all(result.records["m"][:, 3, :] == 0) + + +def test_repeated_keys_same_moment(): + q0, q1 = cirq.LineQubit.range(2) + circuit = cirq.Circuit( + cirq.Moment(cirq.X(q1)), + cirq.Moment(cirq.measure(q0, key="m"), cirq.measure(q1, key="m")), + ) + result = qsimcirq.QSimSimulator().run(circuit, repetitions=10) + assert result.records["m"].shape == (10, 2, 1) + assert np.all(result.records["m"][:, 0, :] == 0) + assert np.all(result.records["m"][:, 1, :] == 1) + + +def test_repeated_keys_different_numbers_of_qubits(): + q0, q1 = cirq.LineQubit.range(2) + circuit = cirq.Circuit( + cirq.measure(q0, key="m"), + cirq.measure(q0, q1, key="m"), + ) + with pytest.raises( + ValueError, match="repeated key 'm' with different numbers of qubits" + ): + _ = qsimcirq.QSimSimulator().run(circuit, repetitions=10) def test_cirq_too_big_gate(): From 9988d43d2ff0720969c9d0226b1e406f664123c8 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 29 Jun 2022 20:24:38 -0700 Subject: [PATCH 246/246] Update version to 0.13.0 --- qsimcirq/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qsimcirq/_version.py b/qsimcirq/_version.py index a2c2c9c2..837c5a31 100644 --- a/qsimcirq/_version.py +++ b/qsimcirq/_version.py @@ -1,3 +1,3 @@ """The version number defined here is read automatically in setup.py.""" -__version__ = "0.12.2.dev20220422" +__version__ = "0.13.0"

-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%0DEenEJ?q$~ZgPpd1E)JHPx=cBg2-JK;Ia1epA&I zhEJ_o&g3K9NWGS;sNriVwHn*g?JI&Y#U`9NeA?tz{J5*G^2A$X63!!LNtOAz_c-%$z{7XEB(f}@C++NWY3PrWX5Z?eX$b=G=?Zs^v5X8ENu_jO`a%y z>5XYOI2&nHKj^29!_0`kldK8r$-b%Q|94iu*+05i0UX)>8RO;l9UcyR}bjEE?<4r zqJJwRdA#gXD0}#X4lOH->*EA%VmWQT2R?ckk4_53ZxwDJvJkCFhoFMpHWQbKYs_vG z8V@bPHBMe(GPlTkuOtXZGzc_U5q)d_K-yylr+HP^=5f38K);2W_%FuU&lX-POFly^ znlP5Zh;kB0nBvLfE}G_=iV0V}xl7V6Fk#_BPs~KVw<2<4BL5PWRBiG5?#s*vYF(Q~ zut?S8C!6I0HGdwOg6=q{m&fAcu6NMTw0jpH4N#3fxE>+U4Q3GkSWa%|z2#BX-11bF zoXP}#MO=$R8zn}BZxoqq3HD-%Ac$d{Qf_jpsa)e^#ZL2!y_ZknPqG#6gG1#~eF8IB}9^5s->Om;o%z-4U+USrYovWFBtJUb0Di#{FQ!(X$@h&!+I41WXR1tF>a2~2R` zD?eA<3j0Cn->H}Pl$f3$GLzT__RF`9(w2*ljUEa++{lkr7+KVfRUlz-CDKH1e|hZE9#q7Io8d)iiHuO**LBq0o<(%VZw_aF$*?bcCZ z9^QtZtl_PVuWLZv4N6Hk84_pa`1)>Ucm$c3T1mQ^c6(vA$9p;Q7b1fAt0rJ!bi3}@ zz*MPk(QVu}PSMVZZ!tN1&f<35^Gd!A=1*O(8J}J8ZA?Qq(fgu#+1Vr<9wT4rh@YEU zZ@e*z(;1f;aN0vm6d_tMn!{IXEqPBU1f1qRDe&fRoQArIybcs(XI*w9z>0gXx<{@7 ziBrWY+O8gBw7I0{;4k=cXG_K$gk z0j(4s;7F%Pk4=B8uq11aja-9Poh6{!mycajqkTz)*CslnLhPXKJoVu(Oc6KXvj5tf zg3xv{wMlv^9rsY+JTK8S<57cf{o+Y&^p$W%m;S^=3%H^1JLjeuCTBV8Iq({X@6NRh z&{w!~mM{;~M-GkeHVUSualWeLl=iWul{Aq@7A8`}-4&n;7r#zy8o}4s9SBybc~WUrpZ12|+AVcj(BgVa zmT#=zVq$!2o;4&fUgGs6yO~Xe=K9di%1sgslR4G5dldf9nL#f%hE{Q%$Jp7}qbp2%;gN$LV83E>eCW)DnY$_6neEjztA`KXjI-k9k3edoBpm^Vo-58bEt zHncr=_i@Jk5lOKX*D-|6S^8|b!@%*^BJ^bV>p3P}*FZBXh5QwpZ~Ff zqmsf~un#rMEa?cOLX>orkcjNvZl1ZHeu15%PQj~qW0MB3Lgp)_Mde(+E3GL`v#-(W z$)ufXdt(%9uQ#EjoK!~09@NQpkIzZVOKSZ5$br=;(L(%n^7|upq3{)9X%gX&z*qx^ zw`KPCUyr6pZmDW(YOL)C$N{-1`n&wpy1us?yV<`A=LB65AFlyDT+e5^o8wXezLWRq zWkUKDQUr*?w7u{=lBi`8El#~ED)ic&u~>Mql$3(z8bdynVO(#8H{M*eKF{Q?DTGHI(Sdx}de- zPxheA%~pL@X+UFVM*AUIFW7-*oULpjj399Uw>4KW8%RbtD@I7$^t(>_T2K5nX%lusBQqc`zTF!m8u$km^& zU`4E6Q0N|Nudn#yeq@%m5Y6v@)vu;-~aZEbgA1WH1u5DZC>k+`dI3E%LrYLC1oy z<5vxM0@!~%fd(D+8yl(G?PSz(@7i(x$@BNk>6w~mcynRQqA3Y|EfE&{b|NlAEz0AU zWiGEWm8nYXhv#R+wMEE*Zt;*r|vm8Ka6{%pk-Hy0Jgh@8v zv+AqMtatDfcdOb*BbCB8FLZ53qkv-i~p`qe?i=97Rp*{liblA0Hsux0gRb^WrlDc%7o zITut>s`1d_BJ&sre~+oI&}EB|Hc+yUOQ^}8JhWd?BCXd$ zJ&S*oVcg4x2b=4}?Op6Wj?Be&DXeCEtoK40agcQJi-QGZqTBwq5nHPdOLq9qNm&%Kr6-5(aF5AIO2dUL`+*P(~ zmku7k2_V5tv8d7cFgLq>=~93zEChY+dXOZe$=&v|fkdt@=t{TQ%1Lz<-j&AsL0-v( zVEChh8jGU(D~iIpoNz?MdVa!9tES4E?bCJ*P?Ti6}t&R=f#^fZZm2js_Y2U%? z=&LU05SY%qZB@rJax%f;c>ib4+zL(pnfJrWknAc4fuUZ~q|ewsV~8ox^8&e70l8Hzd5MSRzn_ z)PHo|vQnYnz}JKZ=1{6-@QUO(<{o1B(%!}yImYep4iu}m3uCMrw3jJjxher#R!s1t zf{lBC#f2D=_tt11Bl+a*TqpxgC%f6zl`p5p~-y)f5CPWkSHV$TAP%$&T4&L+o4?S zX+?zawl-w}rq=2@5k;JVZ8BI$IjSk%3U=L<=~szu32*o{NvfheJoofL{WzkA@M(iNo?$5YJV3^T#xp_osu`S$KT&SaEIb9f_1D-Yp!&?P zJv+;0sbcg`Ulrw~->cO%PuPnW=$Hx7Bk!>w*GADdr-3bwh?0` zjeaKMIxjWxNVani3PCy@1R#9tT$Br^bp)@Tx-J!|Q2uT6gEmy^S;Uu|iey#n=uO0H zVW(wn<~j4&C;X4s@;eHeiiPP$RM#ocQO6CyTDYHytgVW8GUj=R<$Q}=i}Gn9Rd5Z|bd>(oXbh)A!B`+85* ztMo=$Jnv}tvo}fjt*L%w-p7pbZzY*wu&O$CW0@=?XD!XSgF*gi1azYLT+UW>ptQw` zIbbSELZqobwf_PI)-U!_v+|WTC6tu0g2S>T$|8D&yw-WnBAzC9qzonL za})+!JxKITM6HqZ0{Lg=3LvCAnLBieq~>Fj+cMNN0FTv4YUe7GBE$qsj>a9;ZJ|=* z)AdUaG8(!bTZ9_p*lkzt9E&-*QpZ^sZrOqplO?DuO#E)5@G%3pnHg5Qd}8DmgDPbg zgDz1o-`6*Y+rs9Y`K{;joiOfR-(xlno0Zm`>DuEq3g=uyFQu4<+uKR_x;P-h(KUUA ze^auS7*NHL!ETG`kHx4%ea$#~KF7CE4d4s)wB(72LSo(YxW37I@1fj(i!Gv;<}-)U@}icfjwANlTP`}?-z*b@yivp<49 zjF|a?;&w#n%s)La+p2PYhMEtXlBb*(HoN1bc3X_B*@wxo{_uicZh`pkAbf2 zxAfgr0^q=#fZHhiQQ$kXgUh=d_Np+UB*%~1jq4(2JoaS4qnnol$)Um{SH{bxvq{Tw zh`}1S1Qrs3ex|=!dFZeYmc$q)1E?vx-2%Ct;zIB>-pOB19KvnTkFw+7GVU{tS@PPt zTrN!Ia4BNQXtc?K{}}RE8<#k(#SKgiGdhpfRGXIo7)nI6LA_H=(jvX2WF=wEaeQ#X zWiwH<(NF~TCn?=6v36Eb(MZS3K8_l?mZ-zx<9e^`oy_;^KWIVs9$iqy%AF2$C>rf| z&PE88TX6sVgsF@>Xx+1K0J3bYKb+!>>-GZtHCFDIO7t5XMJY~p{Mb4ETc%*c!I*s)q=)M z$&!7*_=7J%&-Zn=Ay2xu1{QPboFg&afZ_%;{QoF!@iB#?&#(s+k}OF~#al{fmuCOM zLhxKYER0<$^rek^zuw)?5{doJMA`wBJZi-vj{m4{Ch^Q1CyYut3WS&7$XRwhZz5gl z#EzWwxdFYhsS8UkQF+7|;38nF9}^b!gUCoPu@RLhsXNGHlC=4YzCb(BD^WUQ zg@^u(#Tz$Vo+r>Yx4a7WWKy#t1a{rOV|3B+QS%NMUd34>988o&O%_~A$w1!B5N&*4 z|AAE2s)CcHicjJdL|9lg2J1@`n{Q0kmG40s!n-K|@wV zg`U2ws}nJ+#mp)$?}t;XV@4{RlYXbfy-u5fN&<_r95gYw&pPb|pzE(3*ErvlJ++ zWl~*w$6}!}=&3decvs$i-+*}%?uid?nKwa)7K*YSu$eG1!$b4yY1PE#dpFN|7Xad1 ztoHF_-{8!9#ITM!W4!KOuf0lz$WDfp-$i;V(Cq=ZCxr|9FVC19>`#F~B9St4CK^}x zN}SR@N;o!vAZd%XX?#AhWhcL{=|*PyS>R8>?gJC93uBEoo8y_I`RDnn_8`fiHLokm zs|o{-EFQbRlTjcEm>ijF(xUnir)bzvC(+i zy@#&(A-+i@4Q`RW(>U0b_UG*{1H@K=#Beg+O|AoI(bnd#LI}JtMCO;0{cm7^)L)|; ztDu$e!TVX%ya*Br6VLYCP0k)pIFY8{wF`d5XrgE#>RvI%^cD&d7jLK7hCjISG8+w~+I}Jvn&K-HN%n%CUC)pX1?>l+y>HLHn9DQzYwH~nT72@hP7%Tq0mxcrFge-~c>H|=J# z`Ma6dApWnk6`0TVS(N&(&7@mHbWg<3zO-<+sPM@7WcS0T3k!p~HKPmV%aPfUJWuNC zV@?-kN=c5MUkzzSFo-?}^)z!13qOq8Bhzt?Wy8a+5?J}4*sP1uN+9Qh$nMXi?qL7(P$@z zgx1sJGA_pAzrk;53-{(F7c~jLA%k0C+FWgac-f_C!fC0knKwqrBt_X!uPoniTOLy! zIo=d}qzWcnC*`5jBdC$|CKAnGbD=oxhkQjd|FQkU`JFwi_JU9ksBE_PAcJLk6@K06I-vJ+T?)tsR z|0pv85rl|~k>;|bp3ZzR-YeH1k;O&? zF#!E%*jxP{0?jB@z6;8_niDM;e2OJ2UHYt)a1*V+{rfQksB(`nWPWedONU1%b{F@1 z>T?fhgaT1HJ`v- zQXl4mN9?xQ#pdwov=Yy0b#@!9eR#7fIU*t+o>#=+lJm?@r_PgyrKdq9rJMT+SA>hB zMgtMKyk0_ciH;<=E$*sKptcER=SS--y2`fQ)J#;2pX1tq+9=IkgNMc#fz0Y@EeMJm zFi9--cNng2!v5%dW9X5$y=wW;4A05%LUl2oL~mAeW2|+?>xNmGh}Z8AwnnhU$$CQf zmhBa5QD}tv@l~Ns<2~F=F&9;O`b`UIG7b|eM8)nHeH6r?BcQpM?5EU#62$3Tiunii zJj%B;LYVi5kh4$>$q|n;wfRvnoksJ_*W#kZ_8_H;`7jTKkx?blG)^AhU-yo{FqSa{ zTUL2lu&O_ZV_tE1d#~Hxw3W?*el(m5@%#uBXwEa4-qG$2RIGs}251#^r zr`QkQwhnltltqk6n&jjj>M@%3Yv4)R;?6${6<3PF6zfm+!8;}|mBQ*CenIjQ+jT*CvyYpScQ`Z+RzQ=mC97XohQ-a>RCsF4S8?^$3MBqbhrXCYJv6F zbn%9HWR6hr@mn?uVpYkQq>DC=0p3KQ0?$tu=|t8mWQ}w(y-O54*l};#%WCYqLFZi+ zHMlsP-ZpV2oSKQm5wu<3^7OHjaPOpj<}C4b!v+&o@Sx|W*^Z!PA#tE163z77%^91h zI&IeGx|{KYl=H&m6cc29jNH-J5RfK))GS06K=mAFA_3}xtE=To=o>bR+c-Rd3BI0D zp6EteY`qd5fXpq?!iakkDoCFkFPev`4q30nP!9Qi*ojb8HKI7?ggB;8 zfJVHAx{ID6#pdb;1kcr;tHm6zD+>Pfhw=DYTPpPTu*b+2tQDmmkbf#IG>Va66*=b z^`s%aF!iE%w_R@ON`XfNNS*XiYtRqLQ9W~vJLKD*x{iraKU;Xfx}x&~G{t7SPN^W; z<$bT@MZ9X2NW@F^2FSN%SAkxy&b!R+QHoOxb$*;|4(YP;FN&X`ri%zS?yl-jT5;#A z#{KE#PIU-_fRhB@4-Vnzu#$5{!*YNuAX~lh)#s|WZw;&0^*uN8+5!#8X>KHV8aQr; z`pD)vZSRyn{Us5Kt2JQl(Bl%_*|}>CbE|<IECPzqkeFF2NvrR+=9(ZYI0?vH|j|u$$zfw0b}cB;&?wFohBIm{AX(~xA$Lbf{U(qRHlYsPG+n}3MdJ{+{OV2F7Wr=YIaTQ&sqY9byb^` zbGMRe8o~I94izsCE)LS=cfJ%83TXh_Y+aAVod7EmL%J8h+GL`bJH^P!>B4ZIr`2!k zst}ejk9FVcE4pmGIF?E84*bv6bBhAAo|3F(IV$NYc3F#PI-NzA0T4xE%t-_7*q+co z`}VKh`~Uw*#|pp+PT@a(ny?kYeaU+5WzvqH|NH#@=Oq8nr{8;D%i^J#-B7u=K{O#^=oK{d zFwUhhreKwD9DQgSwvkKt7b_dQk2dZ1fQ~z@x{%_Ag5&wMVdWCRbGzlhvkQ^|{ zMQ;2$cjvH``+OGz3~-pC{w>-{{*iM44}ioa-~Ov-X}!uL>%TAHZg{g#xHq5XaGyEg z%VMB~bw`U4X8o`&C#z}VL6Zb-zs)icdD!3VFxd`HZ*xe2+3Zi+WTLI|C;w`Z`pvkr zwC;;60kZQyE&uyb0S9;~QGuKkz1m)uJ_{jzm${q+>?I-xllxicZcrr(z+w`?w=dyd zrtp(>Bh-5mJFQDh7`{ymRtB`@=a_+1GQ8Uu2Nu^){84n)FaTmN8?bK_z!(1i(hmQr zDgNh~;^yT?Fc`kMJ9IO~zuEC35hfSL;q?geTS91_5PSOW97>+q@ahW{;xg)Wm?QLi z%d%L!d(PemK$x2_QkjI_v7JkbhPnah_Hg4Ribe$&7xR{n?2+naDq`2%@*IE_RV!>1 zR!kit>pGOT|8RJ8-s}rgJzc;6|L+4lVITlmhf6G(Iobx|-M2abZ$nY7KljQfKb1)| z0C28CQ?c&UsQi@spRfMEKT*(L{y&2+!1nj)>hPmnr7YH>Bknay^>y7*(VM5%vthO% z9B@G{fq&FbOHz@$a&=wLQl{q)sznfIz&OLoy7>>()nAFi6@x2diUD?C*&5`25de8y zpxQco&Na5R^M1S`Mc04vnjiGW&f$CGfXhq%-xdecW&i&D=TS`|e@S~wps7N#9hQe5 z=VHcuCCGVn$$CRhVlIwiFe0wL+V~Z*fV#dG5f}Fe%+XSeD*$CYl+HZg%daW->@k}w zufxccOcGC-S7co_7jNwsSe?DWUxJUEm9~PkFU+ywMsJI{V(zVf$D~2}9aYY;F|JJj zsJvu$W=+TYO@Nl~95At`#&C^`3wE>+)%XZ6Q{vk<=Dq*zzh4oL`T0d4(4{Xv7S@Jz z=5X&r{Y((tE$ltrB0b*(Kp5oFH)0Iqz$GfE8h*GB^rd1%z3a5uMda=VU|9nG%qhz6 z0eD9%Kl(k)2QuZOU-T}u*7w!$$*9wnttM5u`ae;oLLSX>@yvSXmuSBY>e-5aaB40- zt?bo1plwF69()4;pYMy)+Aiz7s8BtT`X%)8zwZAFI*#CfPQdubk4F>$tpX7Oz4uMd z8M$wUsuBqgQWyZh@dxMZC@g#G^bAIJOZj5)5}!To$8(%m{4Vc6g#=^NFg2QT2`n|E z2&H0}R~l4;9~D^c|13g5cFqBCd>vshOl73q6Tr_SZ34N_fEfg9pBenyi2aW_`#+!D z^gI9qO#!ZQxxStS>J1zPFpB|$% zM!7bU`S@|q*Z7HAFUAg1D9saX5oH6c0Rc^5UoOtu$RBWp;+OxYWt&ju;5j)1{;-*! z@#$m!^bv8y_ZR=44-`)(38dfxE~$tiqGT}=&3KU=R&4&>{K*_y8`ONM&IvZ=Z57&s zj%n(#ft(V2XJVRS+$ehXvq*Y(d^!|mnQlsxiT`u^RsRzCoQR?;?f|(o^o~2E#Ba8s zN}nQJaqn~6@g+=D*5BWN2%q$FAK;g$c5gNzH$okcjz^=t&4scru&U=;cqsZ1>eNxT z9CSfYy|Bl(upmnn4km0UZkTGL(1gl>m(%Rl=Rb?Tya8OgEcfX=h1ntavg936LxCAUA4Q=ZPPG8+ z1x58tHYXzQM_oosV4kN-&BDOh%z%uS0D0W?)hId+ShTmZfI3|;A( zMRL^T)hng=iw+`d6uUs@!9O&^?U)8oz7(_{T!MDoGx=eUq@{}h|5Vp-{{Mv zWtwdpt%XH$tqUl zYD9yF<&fAYS~40?<~&VO)BFw>=ZO*!_Z&h33`zy7g~&iO%^F@KZfrVFXX zB^0;9Q)WN!KiN!lzigSHlG_{hPLNLlKO;tpq93m?FUS|aA+vXezBucnKO3Z{Wu9Q; zQ$1f&oeT8^bbg(uWjF=E_YqJ@Hjy010qK?K`^29yVSZRY?o3Y=Bi-W$7~`-SW~YqZ zVE(tW$qVZ{${YxT)n5$}qV?bF03$JLn$%I@WWIERws{6&Rc%HL1HwaW={F;&@z6*KcZg>h1~p;v~S8?SC=$)nQS!>)tbTBO)LTA|*ph2@(RL zfKp1w&>$rYodXgojdZJk`qJGnfD9di^w2PfG(+dN#=ZABd!O&T*Z2K3!^N6st@S*2 z$L}sb7+btPGrKPI`4MUQvn3EfZLdu+@4~8D&o1xNE%ONGapMiciC1gpwu!<~yN!{5 zI=aTz`H{n063S}sN%cO6A+z&pGl#l@>N*1jjO+95SYL*rm*lQ&Zfv{qmn9daGJB9- zUH<+z)P%@G;OH=N+rxwE;J}=qtJAH#j`U0@iO{jYxzKgY&Ph!8J`ErtWMCC@dK8W^ zxR5p;wVE;M4z@wMx3{nw^gh5HjWam_UdsF!lgCf3y}z0TUP6xE7cCQl4?{==>`WeI z`S16fhfYSr>&qcrKD5V>9SH@?y_m7F$zvEsAE;@`oOw7u0M>xbU&pha#-qCeH@gT( z5A*=@U;ryon#qA?Ta2_3w2`p&`n+`>o1$mEkTu1ct1cbMx`10w|9KeOU|YMO=dT06 zq(Gl%sX(reudn8j&NmMp&SM@rHS3n~*Nxt{wA>3OOU1>NJN%|m-Kfw=TUz6gT_4qm z*+yEuo4=f$cY>_2AA2^iK)$oJ#}Mca1-PFqM$}%OU`=7OxHsr}WoCKd25e?H1X2Im z$2p@C=JsVqn?(a68#GfJU?eaodyI6plsxMKnzzP%AK=Vx+DL5R^)Ck)$I71lLjQ;j zbZ;Ei4(X;H+MBRA#>(jvs~;ZfdIq;n5nWc^xvltbf=(iWCL{GMx9r%aagW9(Q8lf< zd2c^RZY9zyB_tqxltKZOF-u|#RIvwo)-ol9O3p!OKAAeHYYd$PT^8&qZ1XV2j^g2x zNb3XG`&;sE^R-x{REMVv&0|Zizi5ZYdaKtIQSN`tnd=;%_AseG!IN+T+t9r2MQwbt=#&^^MMFkGN3$$^pf9;NT_ zo1=1dpEgp{;fsr*YyXVxnfa?DB<-BK8cigV-o$0GvqxdL=2=<9!=lqpvy0Avkj=*k zl=owEoO>W5EdEu%{=>C4{U%Efv7^gOnNvVF!=!5cS~766(Jie{5f>?d6wSc4o*VU- zrylRc1~ol&ya0H@_WWX_WqZR=k4>*-KdH9;CBi?F=7Tf(r;k!mi`n5{dev<0(sO$; zziVY~!`3|)MC2>DO<`G774=%{vufNhcI(&=^`*VX5E{LE$@InLQ?H^c%|M+_f2}fNTTu%PoF1 z5<^Zf5GKo6%|~YH7IbjT!c^u*>6ZEl79|IbwVZ0@4+X9q385t+;qFPz_g=q|1Accb zhDD-+0UF&VCs24iMm#mzgc>+@uFvP6vqKbg_UX(wJdUyMabs&t^2%3r9_g)=8x;_nT>1N-lg4X zQKV$4y6ueD-_=`XveWhZRpSUy=i%w({ZOBM zwqZFLAEp}CWDl`T&qkKS4`kjjI**3bkP5|~c=dS{F>fXNJBzSd!e+Qe)2Sz%hvqXy zOtM>n3ez@|^9a=GD@y&T#2vMW68Qt_Z8vq;N)qMF@hXEx(Sgk!RGCetYOMbXhtY=9 zxS`2RzJ6jQ3o*jDiK{b0W`&(!XsFdJ?x~1>cJsxmpbM+tQIAHTNXRA(e^UA`g-|I( zizQHa{-~yQYt?M1YcDY8?e8~FnaXl+j-LgvsuPbM7yFLNj&siU=xshqJ!=kQv5G`B ztQf7YI!Ws+>$h{*L!SD*Vv%5WRzJC|4cS#=E;eedCj$-zsM^Uh8MGggnul$#p(REb z3JZy92R&4?YjA}o^gJ7amPr?&n~2(HEN4<-t%`?YwXsQ-Q%t+}k1w-u`g?FFNC-C* zX-f!iLS^%_JjTuwq^zhtSbT8W|6#uADzbjPX^!!5uIGrUAlvi*?O`93kv=%7p9wCx zDU_4Z#p$G2Ul7A-NsHF;N$ePKqTASgrJmeG?@l&Nx6DAxB7#PO0VASHbN3!&$nWk&5T1fsW1_eKW%tUd&ly(^E_7eTl^5 zGiN_+)L?6M=PD+E6#>9(G5uhIgly%5pRd+e;AfIsRio2h7qxQ>1QK?X^GiYL z7KL+Tqkr0O1Mx3_wB?VBMb;~G%f-;@+t)uSV2h7G^+{MWp8k$KV}qonIu4Ptj{46} zZA-c9Cy1-mEv?-g=6beR%|G+lpw>NppX$m)>m_W7Zp%CBb#iRcvnG8olQyEY+_;zBr}os-^ak`FST7|M4P&iULFRjZ zl{Q(+j8Nv3Lm*oxJnMQu8*_SX(<=YgDcD;(;lb5605|7ISc1>0Rl^OubzX zKQzc=!4zwZdDb`1%1t=^Z+zA+&wYIffCt!Y_!7hOn~VjgmijHMN$goYo7r5y3VPh4 z)u?&L@L7{M6?STMo_Ewa@S$n$>ZsO*Qpv41fOVZ4_4T!2Zt+t!pH?g!k_l?#ykw(Z zcxcg_s4mxu@Tubb54j|r!0Kn~v8%Owa~{9{+BomlGv1P1(l`UqR&vs@*kxNd-xS+U zqS2oXXZY;_=r?@5>*`S~K#K=e!L#3=6-jTaoz;qp{R#eEj&5^Vrip)!mD_u#;n;Gu z(b@uW79~i9FF*dO9k1If}MqJ=dRU z>3Q_~G6JwUX6oysL<0rW6vYkJP)pyNZZ_iAeQerpw3tEg)+_ za|U8o$t7Xs0E7()w!@~i&vTuJ{P{&@mDgINxs_Rb!;zwd`@mHsLI%@Z(&`b-k91k) zoPZ;P1xDz4sF5Vn%;WTtqLX++t=Q<>k^ORsj0hK1>1l&QfAmGsj5s=m6TN(!wF+0- z@UXEvquxaNu?{*-YS}r_C+o~uC3(2(&?Ar8VTf}NJjfgt7)5DCU6FcpfkorVa!Zqp zie1sQg~Sy{;Uk0G-q**>sIN)IcQ%|Urp111WtJ?7W(m63BX1!OKJR^`qFFnbE25HA zRdo?n0%98qJgPLBT6@EK0i9vpa>OD|&hN8*c{(oI3j3h={I`gEo8E;oisV65MeMGr z+}KrA^xLbC*X)7K(HcI^xT&Rl6VL6{&Tg?Vs-3L3lKu+|peJ($6p7d`<#$i29@KTHP(4zzsT3cgkB|GbD-Y+o4F zFKzXp@-bpuDoBk)sGi3HVafTRrulb?lu}!Z^T%d%K_1p(uYa-+n5*naO?iAEjs1axM|GXllxk%^7$GR&7<%K+{d&<1_E23xSCbs*6lZ+j zf6k-Qg8P?syODfW;HWa3gw%aX!fV?yab%(iEyk~jIt(yl)p-oHow)tJlr|!Y-mVrG zjg*5C<4DqX{O|;$V_J|LvmG6C1aM9h0=$bi?g~QAmVJ`?fhk^da zXF|D?HX}s)q`#0CDoZD4qxU*E;B25+x(n;|Y+{K3OM^SqcL&sDxj(=TatMK@5 zypmb}B%9m7w>c@gr6>Iul#5MaG_#&rwi!xuaSN05RK43DCFdW<*=u}~PWuiAGDy3B zrk|okGAt#}4^8d7l(o1^{TOOu9<_r!MQL=~Z}e7MI`N_tOJuxljH~2@tQQSWWU@B_ z>zqFlS0~s~sldE1-8HEBXW*LIgK{D#hq*7xf5vl#ESUrWNZ zC7<6EiPrYaB>Ik&LgYgoV87N1sH4FJf^+4^d$XE#`zBYj9&&i8YJx!@f_aCQO;BrvE*l?n>V(wk zh3FDihd5aF-sNq?A?LRM*{kR)=RuO-$+# z5=vvC+h^X!`7n}8fs0+9DnCMuxX+}$X_xEixDNtK0$G(Ta;)Ne_go(`a9 zmrHx}n?TH}^7_iH>(5+X1#6Et>6Rr);6kWuee&NL#M>U~8a_a8Rke)Twui_pvFAQ9 zl7*z>CACNUA~^15mvPyKkpMVYAwve`H%txo46@2?w8eYbhr1gkO5lwG3!}9P(>c3O zGQj&_j3qeY5jDaKs2Ac7dtauwsHrhxlNHMXjQ`m2cpGm!UEDB6vHOyts98T2?AAWQ zOxZwd^<8V^2DI#MtI1x8j-JW9${c?cs9;y&ly@@G>k=;VX};Zrw*##y+_3?}0I|&7qNpUnzxB zTDv*a9`ven$%vaWVBnFr)M@9=Zpf3AMa-Y^&f)QIS^idGwOu9q`A*o(5J?Scm8HwO zFt27FYABI1JFz8fzXARTQU6#WX8#R9cyq^_%5`atF6V4u?zrexx zvHTReT_^oTb|RiLT>kkX2=6|t{THMr=Cg43r>xUEXXW@qn)^oKMOS#x?OIpFBQjFc zt?5bV53aN1dU5#nN#J0TmeMA?>a~%;MG5s9SDy<-yPEY~(q8VXu)P@H7=PUM?!sw5 z)?cObaeKQWD`71a@f-bb&rORJ~{%C?5K z&L3)1yV{BRL=$Je&6l$|D@+Pd27rtC_)0&>EfY_L&6Sd;Sha;YT4dH(HX8Q}c1+CA z^aZD?jj5cP_3lC3s3ME^1hPr>WclWkc9t_rUhhHv2b!-Q!XMw?iWEkpxBe{|;yW7-^T(uJl=Z5ka?Ux} zC{2~~K4h1SfNNl642(EU8}hfw_=!aj^OUY&<$~DXd2fiS1+ZLr_=Izl$CKol7S!n2 zw$E>b0;Q?eGDO3SpkfD9mmkSpP8o*}NbN}}C%f}xEsgw$6IWlG z99sseRPJ({toNzJx#6lp{J)wVGASwG1{%Izp9%cylCRbTgw0yiLUQiJlx+NAR>i&E zhziQFvEk!Hh;D}WFyi~nnyA`@R?eu~r5JQ754_gzPAvm&jX zKcRQk!tIc=5!nLMvC<3BoT|gi1yRx;`gzj>aGx4UJa4PQiCLmlBY1^8)3T=i$DMC` z1rOb42k&CM>`fW~HY|A%>mPe6B*FEId-~Svj7=?p_S+gA(JH(eE9Kxan;-XLH{xSk z_yRL1cVD*lxoP-QW3r2nvi>1HQdMW3{rD^a|5RP=yhqdMeHQP5=tAA8EVYBv5Wh`4rcynzWAz+R znt0H`hD`JKof=!2{i)>U087c*$_z9?b`&)rbicwF9aP#KRRRQmbYf5qC4+e~maq&V z;aUnX`mB&gsz z0SpQz6;+5*hjzs+RStDsPyP6Jd_1AKEiPe}Cb&P#I@9=)eqBI};bPMSA0--@c9}Qw zchiWPNZ@IAdeRAj%2=Usm^}^`kJ8kmSTT>2yJ*XbyN@KznXC(GTkEriONfInmdoGs z@)~*AA~M}n-qyn7YVW1YN3bc^5lK*3r(Vn?&y~5M8^WuNZ{Wv_bcT`bQXtgKNqWa0 zhweGDqqtEgp6Vyv@0E$6%F`z|eDHC)mtQdGxGA29&Vg$BJUN+3@h;LG{HComA#C=a z1CsH6%U-DJgs2u?zU4E6vEJi%NnY{dFMogKFW^Re;1V3!Abu6L)I6i>YjbdB!0ng^ac8q2Chs&e1|ZDPphQT zd)|u~_d&|hhnAYVa?^3Mcvedzww1>R330RhC7{7BJ82M%K!4?sO(gZSf6%a^AS-9Z zIg%IrN_ldah8}+Xmt+ z;mMsuYTGj4hH9v&*y-9=`2x*^Z@$0JjlZ639H4)_DM@JI`0;bnwKZ3^GV-7C z{uDk0(+N3Ie%vt?X*1dVmq0#D2a^MkVwhwKH>XxryP3Gxz0flO3n~HxfpNt3^Fs$| zvA#Zb(%XDB-~+2jE86r$!G2FQT5FR9n>5yBGCYZPhRzAyZ)8Wwixa00zgKFOgoAcA zDWj$F5iAunS%*>#6<;ZWx&o37o$xwycC%70OTl$STo7Fz@)+JQq4daOEGW+z_zl}l{Rr~IY4o}__kVZT*w@zXt<<#j4?;gr@ zm{Yd-0on1L0UF?B8)y$I{o~uhIGs3-r|L0>Hxd3lb<(+DYr-@XEH)PR7( zP{0IG$62u9_f0_7+Ied1tz&^&Q-%`G-9d=-Mv}oR^cQ&ba%T`JS3M+71pSqtETjYJ z;9MjmAv8`+AUDt`k|&h8DRRpK>=`!Hp?RkwiCXy$jYqwMyzg&$Syczn4l?xfHxkLV z@1B<9t5dI(85HjqtPseBgD{R;sbEZ5uk92)=~ELQ^F7Oci((q1%*OW4O4-h#bIw!d z_j)L7ma19ugrX{6FD>j??u$yII52apDxEO^7sf<{3_qO|D(lS+X>C;CLQnLAGZHNLbpcef_Tk z_^}b_VzOI1T!_rW3JmoU;w5RzZ@N00k}Vvy=|th#%ur7bXtr0F^&G^WQ4N`}fH6k+uh%iI z4}H^^kLkG?!1krm8D3!b%O8JdD`e?GlxxH(n;i+1Ma1yb>3jKhlHAVZfCRw-WoNU_ z>I7?W>6w+(ph`xcv%#jXXvzrxeoWEFitmljz~}%sCo`jj7S|`u`uYm;QW$vWt;fXW zLh~(|C)~L48E!omruV$Yr=4;Gf8F;aSY4;52ovqWk~WfA`Gie$ustTuAa>vO361#2N>v!K;|IqL~8Mup4DVr)4=O@&vh zN?<*1zgmpWV@>0}b0A&0IrR$*;8j_=`;A^O%zdkkL?Bm!V)Pc>>rfg&_Q;S~<4zq#ZH&-` zfFC%t+hrO!dfa%RHBA>YCOOQwiwvQx`<_m`i)cvd%i5)8hzR55&W#HB!Kr>sEU_=d z#pLt|yu(+x2BcnYE>eQjrqWfXBMy*uM5(3;^B=ue?Tbo~EfX60GWOh0SQl%BZ%JO_ z*tLwpeTat7ob>(s=22G<^;{FUND%%*rF~ZwWIvBpy&#W5%|`kjQO;ZUp*RB|d<3^p z`^J&ooj}4vO|#cKcFkDZ;Hwy&JO86?T?A9?fT(dIXy|j0&tOnZg;p;nrtptuu9x{N zjF9oXl|=i|ucc+vCD~}7Nya*}36i`mo&1gBQe5NCJ{aC5X>nEM*PZ&7XMak&qR%du zJoPG6O>De$(Lu&EHo8*>{-(wC)zcY$kz}PQoTkkg`U#1lgCy=H=0<^U8m_T_D9t3g zd^Dk7!ph^x+{z;*X^v6+ElLCv8{@wFwo&|-ZxdPfS})wUoDXFLaT0WHM|~GP!)2YO z(9Tvprxr?4iTvW}&p=pFo|a`ot3z)KTK)p$-y5thM6x9@-LT6MnOjzENF0v|3)?wf z87m(%02msby}|WQc{iH_w3r*v*LI;z%@3rcC+G*mv@-EcVD+{pgc9c^BZJsPtMm{i zOTGM0XGhP`ctH})kRyMVC=N8;!94pi5KQq5Vu^>hgKI~G!84tC$V+R!H*izXKiR9( zMFN-4Bd`uEZ|*~MQ2kJdHIMG>(2*+YcZ(g@=NCwj%&dAi%=3p*jVhV=gd=LjQ znPdzDPdv9Wk5tYo0eq6~g1wo{u4qtP z*U1~_vgzthZomE^HFVro5r(JhHq`mK1#y>{+KEW43r1|hz%(;kxv<)U$+5G#BaL0tT?vsLz^fg`50^~gVgUb#7^H|%&zZcaY2WaUE5ti6>h4x$!y52(c zx=XT4vzuZjigWYy)7301Rz_VdoDJq1fH7lv@6}5bPUrHdR(zTw&;4m)R#o!NDGq{L zPx<`G!i+Vl?Il`qMnx@y*()~4R*UFp?af`LANb`s9#b&I=|5k zhfb|;;YqB8Qd)CUy%jMl+JrWwEkkO89qRk8?7^*4e*h0oaXw-)^^UdHncb{hbA6o)+p`d3_w$HIEfi6ZIkhipiqsokog2>)3OD^i$a)r(Q{V0P9MWvNy|_ zDv$(9!Sw!(!g6EXW53OGIV5t)k!reDteJAbU_?AQic@R59zZMk<5>Gey*%}Q%k#k@ zC(NfLLfA)OFqciP1<1e8zYhdFhntkOFk02<`l+!oU9>>+N7mQ*ctW-M4qbPqzwK9^ z-8an@P^1~EixA$yNm(8HRE+;iGPU{AyB%m_Nlh`x*NNNUCu8zLxo(BbYi4yG*UJn( z9zjODHMOL$?&la#2dL`x#U^1Zy{LOu7|p&We!r;gadVE&ybn{>=SYn-qhoGpi;^yK zvAka575mUNnaHo+6{`J*tlLSRgxIjM^l$S4~neOE%;04Ft) zBQ)?mVXoahaJE4Z$9QUs;JOnqYauxtSY0gX(`~&E~j4&6lLEq9Uv!7dgoNa&a0oV3>mPjy=cw~2Z|1_T7lm&0PQ+&+$w*7f}_y{rul zVn@dYt&z3@(C5Muo|RQ!+}-)Blw`M9+6g1^ji0P$3Fv?@P4ZJ?&udAA01)_`x$c~; zeTtl|S6_<04!nGOo@?lZh8&=nsw8_KM(IRfl2^;>iQdW%{#w}eS%;bKo zt&6b%`_b0@N+Sd~0%ADu#e9s$vSV11{$is6$+Vtz)N}0mbRwW|;tTs~Yi+YV6Xs z^kQ?JVzg=94O7=XO*t^ml!S9lS;-nc0o{)2hrMg_n=ei)U9KP+DyEf9PIUJqq|l6) z85?yTJh=TPXKf>aBef?XU9P!>;v?1P_lG(IBJHgWdQ_sA^_OOzonhyekX0NS>67vv zunN3};8-)@_J>8|zKY&KUs7wx2&qiYF<0JTg9m zhf$ND?vXvH3U6`&gRN~dApl;h!?_Jtkf^E5DGDJi17f#FQnGpTIaL(*|- zXqPRqY@~Kovx(DXe`g`g7Lwo?!2fP6q9sg!Gb|(Z7jE{@`n(ThL%L`*RX#26t+w%- z1|XBJuYmJ|r+D9fO%2{2>INm@e94NF$H1ss0sG#Tvl#gOJU;*ee?>}t5tOpnPE=?74WZH6Gm$lRV1AOiNf*|p39P& zPtOO!O5Qe>OKK=)kS~z!)SL45SQIOMQ*&tjhbedw3k2=HxP}Bs|x%Qi_TQ;TS4s{3|2tF^BQZVO^^S%qH- zAD0)|AT(Jd?YGMiPFjX6(fo>}hVjQG4zgc(cGRQG)Xtxp8J!(`k!xOkRlI|6J;O=y zep^2H!VK*>QqeSVl}CB9{fR7mI?J$uBNHbTH)P%IcbdT6T8LGw!U$^Y!=n zpr&9^=tB_b@deI35o8o zAh^85jD^5L{yBjK67fF6rP!%WA%uRr^-uB>&Rr`hGa* zUa*H(T?g~DXa11(Z3`nCsgeUQdcV^br5~)?T5do!&I%rI9nN(n$cg@dRDxIR5daxD z$m*&=h7B=NB-T1Xc<_Yn8ST8n1_%wO2uRe<*Qk&o)zd<{`K&Feq@yg$KM_bKZNC8O zPW_wiMGCVSo%Hz;^hdueKj-d!X&T*Wg3zNEz->hN+tVSz$tg%KOKBEDYXoZC7eix; zdtA>_xr^N4n*;qg#3fDdrEz+0{Kw>l`2e_jVdV%~SmoY#rXtkuK_= zt7ii=WAgYf+__-)vo<72{ZI(tg1TAgS;YkXlHJJz>5D#z_rhfF%{(gIlkzV~!DQ6| zLzP^?+x0|v{S0r`1FuFOmDdm;mL$aHo>=x zVebZ#2eXAp>c=<-49>IN%(Rd^TlQF{R|@ye#x#AJM8(q-qtyHpjJ{gi-CW(z&bBDh zc`iNlOT+p?2LxMpCA8pX4&*JZ7v=%MtZ&&wK@wK>S_S=cN-ErCw$z3&?5t?Zt?uI) zt$8)~Jzf})IFc2w<;5iU?q3=kOx5b_b2EvcSMWK)Im|&7+~2cX)sXW{8qH=o01ukw z*bd zhVN#w0A4Jah>i@=JJdHkV#Uc`JTeNl9$4~Ey!NtRn5dD*w-a;NbfHB8Zhn~TvS1K> zjgK#-@w?)}y@&bPG!MK;yK7Zg1KbPmpFZ2QHHWJrP@=jIda37eP4@7f(D{6NqSew+ z^sMuT3gi+PXpmO9#K(J1R$zC?TrkazdP|7_Eqt`&z!AENu%6<&wJW}qEqBt}D@nA_ zo}kwJDZiZ+fAS7!;U3ZL0+hT_UQ-PbuHBd6f2GtW^i9YETrSEw;1*1Y=OnpF7VhH) zw$+!jOS;3FO;iON3{AP;H|P*Pc`=`dPWYV`>aUXR?XNv;Ldv6PZj99Mmh{>+X`2Rc z6*b>+Qsq^vyIB@}yjX!a2J>`EkITz{|8<>cuoWr7_yUI@oq1H^4eRpa42 zFo+UmXlz6%lwxbi2IoLN8jAWrD+YV`mn}R3KP3TRww6c}mF-H6MY2>siO0VgydQ+Y zzsUb&JEUEHW9xYXACv)x?eudX=ZRwHk+-)XR;*5$nK})$`XUjIwpkkB{(&usj^*G4 zYc~n)J6y-{9sbZ)jA<5*sidiqeRupJb*EWfqOH}6+e~qu^etDHTpYjF*w;WwQECV+ ztD@ZuvJw1LAYhRJ@{n>ZD#7jD$nSfKhVYRZwRw_qVx_O@R-a|NcAPJ1qB zsRl|xaU7h0hxvZKv7w3Rzhu5 z#D6~!@}gdLVf48ajl^YlNX_ZJz7u6j2z>$_V1tHo=IY$nU&I?OalD}X z)8SEDPGaPo7PG_q6XFT?i+5?4+MtCqvW4Ag73l1hY|%MWi+r}HwL&ED_HeX^I|m5! z2c3mx5UFV4+#_621X1=K=D%0Y`{7kh^yYd>HS7^E_ElyiU|r2@x<*h{9af z*@E382NS4;jsZ0xE{Z^i_hBc^SJibA95QgsF4I@My2sNn$lK`7nZV&aZtssK6}}c} zPI@F^4C_O*y!!r$@)k<9RY7WfCs1xRxcLY^TMp3PWVFVn&azpLNX1QN>`Xiw+RO2_3qIS+~YGrV5fCf^N%U(X;di7j->V<6b z_sXi0U3hhwc<*0+jo!0*k4cDx9?222E(Dk_pN?Gdv&d5lebBDoj*0E=dY}gl#+Uj| z{wxQl@p-0B`%;qdPj9h_2QEC!miZ^a79+omHgCVTtZmV;1yOA2WDP8uMOh1{6pgl` z2ia##)^vXN)wREjQpSPRcGhLFiM(kczk5F$yf)6hWgsU;8xQNnFo!Q0KA@%SG-(0K z7<#=6u4;trN`4aFX6jF}A7}PT8Mi%X&9A@%&G?(wzO6KeXU#l4Y38l(!%1*;d^$J5 z8#ECDW_zDh)MU9g*}QgLdCUd!O&YtrP74qbojMM~dlDzMSwSs6^)VODbFW(4;nLor*yy~_@Os*? z@a4vS;h3(2)++@nJQ{^5J!NrfPa?c-mWpRK^H;|xiM1?@ENrJyovUYMaP3Fhn$Mur z5NziXCGmFtctz&S_VIPVE4@omiFMNo$HEm@Rps3A>+9B&*YkZP7xOiX7maT9$6wyv zIa^IgrvAB7Qzf2oeetZ3;OHaXjs6Ico&@|hrZyS1oHgNd$k_x^?40Y_@NmC+u|d(b ziecr8UYcVhM3aRH=LQ^v#|8oeu5tg3CpY^eFuP|GHuwDuls);oK3xYse*^qmhzTbh zQNU^N!g!)Jx*%vdO1y!HEj6~}Sa`(i4KxNSJCehDG}DQ9AJ?=m(5@zeh3J;ek)0I& zC0`yx?dKpdcRD$S6zoFS`+$I3BA#;#-3+Nu?i}fOQ7yAXqi0N&A~v%wF^JCw3huvI zR*5)piCH&4kUn=Iwx(Tq?k|Lg$s+Q) zU2+c1rmK&Zuu~ZIA$sg4*AcqcssiNy{ffY?yQyxMKugkcd=VET`D)8SL%QY0*Ia8` zweu|3ep3GIz!ScBB){=#L+YX7n(=jOW6kdHj_MJI=h{tqq4|1$uj6fLNUB4#aB;@h zYL*Wzd)h(ey1gzAk)2_(avF>g=dUuaNMps#j(<=DYBwS$1I3S;k-yAk?h|dk!ZF0W zpwu5xOh`x{UmJVlH$pM0ZusUup3gEE@fsgiOcjK3?V@2DdDDY!#G#o?#qJtU1e`^^ z(Brb$#Qlfz{#a??Si!;PC{V~Lj~_dSzqIt-)QzXx6J|lpcBLVx1WvVZR5o$HBGU|c z{zXlRzVR;%rpmRhG~3GAV)=9}N06FJ{jK7S{^(nmcHFs~diOh4%ez=2gARuBuMYVi zKE)zZd~`mw3kpWD{@cMhv+n{WU|h29$7LN9ksmaBn~5J50hkbiF_Z@5*K$R~kS|%D zhV^~#u*a<_zwmI!C-wpuBOFU9)3RC7w=_~{zm`Y?v8pfvC}@XvvvKni_Q557OU(Pj z{3~?|)Tz$PP8yFxs%vNJ5?eFtUQ_EGz; zpI_PTKpDR3&JYFNikl+S_3)@BC%xycUrn)rQJ6U{t3N(3KkKM3=3OnkTQVtjA!|Ut z69+B%ax~|?94#FAP3H@8z9a(L1Z6&RHwBLN{lAWOF5q}}f7+q66Rq=HN*UdsMjcB9 z?BB!x-k$$_6gV_{YY0I>j1CA0*fYm7Joo$YNx1?5;h^}qiod}=*sb~RtGDd;@_mZn z78etHclELWXYL4r)rlAgIsXnX*5VZsGcIXlN9< z*kbQWgns;<jS(gt{<7juMXOfM zja%YB9&EN$S50A+!C+8yp3{|dX(fulWPaf*PzZ1M4ijR_O4EGMmDGoWv* z+`ANcx6POyV%E|Gjed4<*#DUFpYrbiIzOv7pb#JNIm>CC7s`*0;WjERlVu8~ep}W4 zC}2b{X9R1Ycc3%wz+dB0uLp+5JprJYr$$B@i8uRS=FZgtr5FG=C@SrTk<#^s5>w|j zH=F1d>Bk>CaGm9+nBI$yj^+?Z-Ds|@sX=~f1}0&q2$=k($jh1P;Mr`mDtEH*2A_VF zkl8&>=5*!59?M0@|Mhr(gfs}RLIu<&=&Jxzp(gL<4%iPFYlMlTW!XI zQ2NEQO|MxGJ^umikow}r!*L_aA0}(*d;QM&DleoL zk<3sJG)66_DpE)(0fWNzS>>#nI6_XXQ%rQ(l4sEA&4HuOBsvXHuNs%$^X+qLLy*T@ zz8M1mhnR215TPq=hX!Gfgd{2s{)TqCS-^Oh&1RizB}F`#hiSm!m}~NBrW9-i1sgit zQ$%*q^dAFIVv>z0(&<19FngS9@V;KgTt|PJXmcZ!X?t za;lxB&Crn9Q<`#Vy=;%WkU@0-uT?XNy;-4t$)|d8JQ#=C-;iJMCKVt7=A;ib5_wY1 zU;|=%Z5bc9p1r3;9FVr}y~f8VN7D8H3Pc+p0|4vD%4)tS^#V}7`z}r~9cpW9z#g_? z_Ye|NPxbL{-Uoy?0=&?d_@3~9x6l^f!&j#OgbTe~mW@Z@SCb-gJ)wzBk7Dz=;b9>w zKbra=0I{VfUChBEhoxOA@ZyBs)n@lo-SmZw6H|$@ki?z8J0Q3`18_q3MW)K=h|oqg zj`2p$^~%%J(@S4Q`<;u$O#j^Ip_qd)z7Ne%@i)XJfTq)J8T*hngX?)F&k=i7YT5Cp zx-;NDj((}EZK$`ye=BY6@ze{8{`xCnS7jn~U|dg6Z${{7N@=;uz~yKdeHqAvJUz?@ zBo}J*{N5C|aqXAELM&4pz^RZI;Iy zlCGsxKM-%s;f_8(KVM$k+S;n-tnl2L*8i&40W|(4(Cv8yJbM=X^>t~c``p9?ogC)j zF^U6_mF2sooHhqOhopE6f2X+Oo&i#S%D?mofa5~KpB3duxR_?1Zgo1-gaZ@HSq+~U zG{0ZeGj>&0C?ZCS+73pAVE`eC)B5cG#sz@SlLE*kB$uGSQPd%zk3JyN+gjY$wT(=5 zgARVE^tGICGm@eyclkG2OqK^C@MnHbxeqH;I8Iea ze)gYj@E92#rJjFdlPGA~B-z4kv>E17X}}5)fc*tv!stRAbKFOsir5Zj>I+TaK4lHM zaBcKQO-xMm0j3dOy2l@5_+K{ce=u^zQ{VwJcE+;rm6(@&a^s0JLc|=ImW?+zK>=nw zE!JqH#Bk4QU~RDSEz7`-vF$Fymd^C)0b9#?2Nbyk3cC3BM2^JDhRra7Se@6uY!?{g zzY+J>7^Fj;uc~BGU0r|d&KX~fQK_fgV<8Fczg-8=I5l#(L};z?I&r~-b7A1mYtXLy z-w402p}_T=RYy&ef7Qd*C?EzEO6lq8p1rdfCr$pRZa=2y`SePnNX*X0Vu&lZZY<=qZyYJ8a~}T zQ$BJ^X*8Bcbkq zpTe58W1Y860yu#Fc|hwH?%%B1PmMJM`5p_Y03Y0$U;f=g`#0)F5Mh^8Nd5ep4-bZi z(K;z#N&YWS?%%Ehz6w1c2K>UoV-anyFs!Ghox^z){GXTni+$wLz@@h5G#sfMzz6l< z=-<5%9(GB#eyuv15GladxzKl0`x}zIodvkQDre$vgO#s=o!G;ix(fY!>stY9eG|cMIse&B61im5&A*Y^kGTO&_pY-1f;=u~nfIh5@C7Gnt^#&A-Sb;_Q>lr=FXJK2(uJ!C9{Da(j4 zCdSMdV|ng5-|z4F{hsg1b3M;L=eo{yh5P>8_x)a8@7MeF?q}ItTPSI(u$v?*o`FNW zQq$i_z6f^8xotB0_J{|6zdxQx#JFMuXrm)cfd?&zRc5A7?eKwq*)@6 CP9DXpF~ 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 hfZwk)r{n-#<?!X3c8R?np(#|^G`VW98XEp!; literal 0 HcmV?d00001 diff --git a/docs/tutorials/gcp_before_you_begin.md b/docs/tutorials/gcp_before_you_begin.md new file mode 100644 index 00000000..7dbe05c5 --- /dev/null +++ b/docs/tutorials/gcp_before_you_begin.md @@ -0,0 +1,33 @@ +# Before you begin + +The following tutorials demonstrate how to configure the Google Cloud Platform +to run quantum simulations with qsim. + +You can use Google Cloud to run high-performance CPU-based simulations or +GPU-based simulations, depending on your requirements. For more information +about making a choice between CPU- and GPU-based simulations, see +[Choosing hardware for your qsim simulation](). + +This tutorial depends on resources provided by the Google Cloud Platform. + +* **Ensure that you have a Google Cloud Platform project.** You can reuse an + existing project, or create a new one, from your + [project dashboard](https://console.cloud.google.com/projectselector2/home/dashboard). + * For more information about Google Cloud projects, see + [Creating and managing projects](https://cloud.google.com/resource-manager/docs/creating-managing-projects) + in the Google Cloud documentation. +* **Ensure that billing is enabled for your project.** + * For more information about billing, see + [Enable, disable, or change billing for a project](https://cloud.google.com/billing/docs/how-to/modify-project#enable-billing) + in the Google Cloud documentation. +* **Estimate costs for your project** Use the + [Google Cloud Pricing Calculator](https://cloud.google.com/products/calculator) + to estimate the scale of the costs you might incur, based on your projected + usage. The resources that you use to simulate a quantum circuit on the + Google Cloud platform are billable. +* **Enable the Compute Engine API for your project.** You can enable APIs from + the [API Library Console](https://console.cloud.google.com/apis/library). On + the console, in the search box, enter "compute engine api" to find the API + and click through to Enable it. + * For more information about enabling the Compute Engine API, see + [Getting Started](https://cloud.google.com/apis/docs/getting-started) in diff --git a/docs/tutorials/gcp_cpu.md b/docs/tutorials/gcp_cpu.md new file mode 100644 index 00000000..59b52ef0 --- /dev/null +++ b/docs/tutorials/gcp_cpu.md @@ -0,0 +1,247 @@ +# CPU-based quantum simulation on Google Cloud + +In this tutorial, you configure and test a virtual machine (VM) to run CPU-based +quantum simulations. The configuration in this tutorial uses the qsim Docker +container, running on a Google Compute Engine VM. + +## 1. Create a virtual machine + +Follow the instructions in the +[Quickstart using a Linux VM](https://cloud.google.com/compute/docs/quickstart-linux) +guide to create a VM. In addition to the guidance under the Create a Linux VM +instance heading, ensure that your VM has the following properties: + +* In the Machine Configuration section, in the Machine family options, + click on the **Compute Optimized** filter. +* In the Machine Configuration section, in the Machine family options, in + the Series option, choose **C2**. +* In the Machine Configuration section, in the Machine family options, in + the Machine type option, choose **c2-standard-16**. This option gives + you 16 virtual CPUS and 64MB of RAM. + * This choice is for demonstration purposes only. For a live + experiment, see [Choosing hardware for your qsim simulation](). +* In the Boot disk section, click the Change button, and choose + Container-Optimized OS. This overrides step 3 in the quickstart guide. +* In the Firewall section, ensure that both the **Allow HTTP traffic** + checkbox and **Allow HTTPS traffic** checkbox have marks. + +When Google Cloud finishes creating the VM, you can see your VM listed in the +[Compute Instances dashboard](https://pantheon.corp.google.com/compute/instances) +for your project. + +### Find out more +* [Choosing the right machine family and + type](https://cloud.google.com/blog/products/compute/choose-the-right-google-compute-engine-machine-type-for-you) +* [Container-Optimized OS + Overview](https://cloud.google.com/container-optimized-os/docs/concepts/features-and-benefits) + +## 2. Prepare your computer + +Use SSH to create an encrypted tunnel from your computer to your VM and redirect +a local port to your VM over the tunnel. + +1. Follow the instructions in the + [Installing Cloud SDK](https://cloud.google.com/sdk/docs/install) + documentation to install the `gcloud` command line tool. +1. After installation, initialize the Google Cloud environment by using the + `gcloud init` command. You need to provide details about your VM, such as + the project name and the region where your VM is located. + 1. You can verify your environment by using the `gcloud config list` + command. +1. Create an SSH tunnel and redirect a local port to use the tunnel by typing + the following command in a terminal window on your computer. Replace + `[YOUR_INSTANCE_NAME]` with the name of your VM. + +``` +gcloud compute ssh [YOUR_INSTANCE_NAME] -- -L 8888:localhost:8888 +``` + +When the command completes successfully, your prompt changes from your local +machine to your virtual machine. + +## 3. Start the qsim Docker container on your virtual machine + +On your VM, start the qsim container by typing the following command at the +command prompt on your VM from the previous step. + +``` +docker run -v `pwd`:/homedir -p 8888:8888 gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest & +``` + +If you see a `permission denied` error message, you might need to add `sudo` +before your Docker command. For more information about running Docker, see the +[`docker run` command reference](https://docs.docker.com/engine/reference/run/#general-form). + +As Docker downloads and starts the container, it prints many lines of output. +The last few lines should be similar to the following output: + +``` +To access the notebook, open this file in a browser: + file:///root/.local/share/jupyter/runtime/nbserver-1-open.html +Or copy and paste one of these URLs: + http://e1f7a7cca9fa:8888/?token=aa16e1b6d3f51c58928037d34cc6854dac47347dd4c0eae5 + or http://127.0.0.1:8888/?token=aa16e1b6d3f51c58928037d34cc6854dac47347dd4c0eae5 +``` + +Copy the URL in the last line of output from your console, and save it for the +next step. + +## 4. Connect to your virtual machine + +The easiest way to use your VM is through a notebook environment like +[Google Colaboratory](https://colab.sandbox.google.com/notebooks/intro.ipynb?utm_source=scs-index#recent=true) +(Colab). Google Colab is a free, hosted notebook environment that enables you to +write, execute, and share Python code from your browser. + +However, the qsim Docker image also includes a Jupyter kernel and other +command-line tools. These tools enable you to connect directly to your container +and run your code. + +* {Colab} + + You can write code in a Colab notebook, and use your VM to run your code. In + this step, we use the + [Get Started with qsimcirq Colab notebook](https://quantumai.google/qsim/tutorials/qsimcirq). + + 1. Open the + [Get Started with qsimcirq notebook](https://quantumai.google/qsim/tutorials/qsimcirq). + 2. Near the top-right corner of your notebook, click the small triangle beside + the Connect button to open the menu. + 3. Choose the **Connect to a local runtime** option to open the Local + connection settings window. + Google Colab Connect to Local Runtime button + 1. In the Backend URL text field, paste the URL that you saved in step 5. + 2. Change the part of your URL that read `127.0.0.1` to `localhost`. + Google Colab Local Runtime connection window + 1. Click the **Connect** button in the Local connection settings window. + + When your connection is ready, Colab displays a green checkmark beside the + Connected (Local) button near the top-right corner of your notebook. + + Google Colab Local Runtime connection windo + + The code cells in your notebook now execute on your VM instead of your local + computer. + +* {Jupyter} + + You can run your simulation directly in your Docker container, in Jupyter. + Jupyter runs in the qsim Docker container. + + 1. Open a browser window. + 2. In the navigation bar, paste the URL that you copied in step 4. + 3. In the browser you should now see the Jupyter UI, running on your VM. + + The code that you execute here executes on your VM. You can navigate to qsim > + docs > tutorials to try other tutorials. + +* {Command line} + + You can run a quantum simulation using qsim from the command line. + Your code runs in the qsim Docker container. + + **Before you begin** + + For this scenario, you can connect to your machine directly over SSH rather than + create a tunnel. In step 3.3 above, remove the second half of the command. + Instead of the following command: + + ``` + gcloud compute ssh [YOUR_INSTANCE_NAME] -- -L 8888:localhost:8888 + ``` + + Run: + + ``` + gcloud compute ssh [YOUR_INSTANCE_NAME] + ``` + + Either command works for the purpose of this tutorial. Continue to step 4 then + complete the steps below, regardless of which command you use. + + **1. Copy the container ID for your qsim Docker container** + + Use the `docker ps` command to display the container ID. The output should look + like the following text: + + ``` + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES i + 8ab217d640a3 gcr.io/quantum-291919/jupyter_qsim:latest "jupyter-notebook --…" 2 hours ago Up 2 hours 0.0.0.0:8888->8888/tcp dazzling_lovelace. + ``` + + In this case, the container ID is `8ab217d640a3`. + + **2. Connect to your qsim Docker container** + + Use the docker exec command line tool to login to your container. Replace + `[CONTAINER_ID]` with the ID that you copied in step 1. + + ``` + docker exec -it [CONTAINER_ID] /bin/bash + ``` + + Your command prompt now executes commands in the container. + + **3. Verify your installation** + + You can use the code below to verify that qsim uses your qsim installation. + You can paste the code directly into the REPL, or paste the code in a + file. + ``` + # Import Cirq and qsim + import cirq + import qsimcirq + + # Instantiate qubits and create a circuit + q0, q1 = cirq.LineQubit.range(2) + circuit = cirq.Circuit(cirq.H(q0), cirq.CX(q0, q1)) + + # Instantiate a simulator that uses the GPU + qsim_simulator = qsimcirq.QSimSimulator() + + # Run the simulation + print("Running simulation for the following circuit:") + print(circuit) + + qsim_results = qsim_simulator.compute_amplitudes( + circuit, bitstrings=[0b00, 0b01]) + + print("qsim results:") + print(qsim_results) + ``` + + After a moment, you should see a result that looks similar to the following. + + ``` + [(0.7071067690849304+0j), 0j] + ``` + +## Clean up + +After you finish either tutorial, you can avoid continued billing by stopping or +deleting the VM instance that you create; visit the +[Compute Instances dashboard](https://pantheon.corp.google.com/compute/instances) +to manage your VM. + +For more information about managing your VM, see the following documentation +from Google Cloud: + +* [Stopping and starting a VM](https://cloud.google.com/compute/docs/instances/stop-start-instance) +* [Suspending and resuming an instance](https://cloud.google.com/compute/docs/instances/suspend-resume-instance) +* [Deleting a VM instance](https://cloud.google.com/compute/docs/instances/deleting-instance) + +## Next steps + +After you finish, don't forget to stop or delete your VM on the Compute +Instances dashboard. For more information, see the +[Clean Up section in the Overview](https://quantumai.google/qsim/tutorials/gcp_overview#clean_up). + +You are now ready to run your own large simulations on Google Cloud. If you want +to try a large circuit on Google Cloud, you can connect the +[Simulate a large quantum circuit](https://colab.sandbox.google.com/github/quantumlib/qsim/blob/master/docs/tutorials/q32d14.ipynb) +Colab notebook to your VM +([documentation](https://quantumai.google/qsim/tutorials/q32d14)). + +As an alternative to Google Cloud, you can download the Docker container or the +qsim source code to run quantum simulations on your own high-performance +computing platform. diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md new file mode 100644 index 00000000..6ce85650 --- /dev/null +++ b/docs/tutorials/gcp_gpu.md @@ -0,0 +1,195 @@ +# GPU-based quantum simulation on Google Cloud + +In this tutorial, you configure and test a virtual machine (VM) to run GPU-based +quantum simulations on Google Cloud. + +The later steps in this tutorial require you to enter several commands at the +command line. Some commands might require you to add `sudo` before the command. +For example, if a step asks you to type `icecream -fancy`, you might need to +type `sudo icecream -fancy`. + +## 1. Create a virtual machine + +Follow the instructions in the +[Quickstart using a Linux VM](https://cloud.google.com/compute/docs/quickstart-linux) +guide to create a VM. In addition to the guidance under the Create a Linux VM +instance heading, ensure that your VM has the following properties: + +* In the Machine Configuration section, in the Machine Family options, click + on the **GPU** filter. +* In the Machine Configuration section, in the Machine family options, in the + GPU type option, choose **NVIDIA Tesla A100** + * In the Number of GPUs option, choose **1**. +* In the Boot disk section, click the **Change** button. + * In the Operating System option, choose **Ubuntu**. + * In the Version option, choose **20.04 LTS**. + * In the Size field, enter **30** (minimum). + * This overrides step 3 through 5 in the Quickstart guide. +* In the Firewall section, ensure that both the **Allow HTTP traffic** + checkbox and **Allow HTTPS traffic** checkbox have marks. + +When Google Cloud finishes creating the VM, you can see your VM listed in the +[Compute Instances dashboard](https://pantheon.corp.google.com/compute/instances) +for your project. + +### Find out more + +* [Choosing the right machine family and type](https://cloud.google.com/blog/products/compute/choose-the-right-google-compute-engine-machine-type-for-you) +* [Creating a VM with attached GPUs](https://cloud.google.com/compute/docs/gpus/create-vm-with-gpus#create-new-gpu-vm) + +## 2. Prepare your computer + +Use SSH to create an encrypted tunnel from your computer to your VM and redirect +a local port to your VM over the tunnel. + +1. Follow the instructions in the + [Installing Cloud SDK](https://cloud.google.com/sdk/docs/install) + documentation to install the `gcloud` command line tool. +2. After installation, initialize the Google Cloud environment by using the + `gcloud init` command. You need to provide details about your VM, such as + the project name and the region where your VM is located. + 1. You can verify your environment by using the `gcloud config list` + command. +3. Create an SSH tunnel and redirect a local port to use the tunnel by typing + the following command in a terminal window on your computer. Replace + `[YOUR_INSTANCE_NAME]` with the name of your VM. + +```shell +gcloud compute ssh [YOUR_INSTANCE_NAME] +``` + +When the command completes successfully, your prompt changes from your local +machine to your virtual machine. + +## 3. Enable your virutal machine to use the GPu + +1. Install the GPU driver. In the Google Cloud documentation, in the Installing + GPU drivers guide, follow the steps provided: + * In + [the Examples section](https://cloud.google.com/compute/docs/gpus/install-drivers-gpu#examples), + under the Ubuntu tab. Only follow the steps for Ubuntu 20.04 (steps 3a + through 3f). + * In the + [Verifying the GPU driver install](https://cloud.google.com/compute/docs/gpus/install-drivers-gpu#verify-driver-install) + section. +2. Run `sudo apt install -y nvidia-cuda-toolkit` to install the CUDA toolkit. +3. Add your CUDA toolkit to the environment search path. + 1. Run `ls /usr/local` to discover the directory of the CUDA toolkit that + you installed. The toolkit will be the highest number that looks like + the pattern `cuda-XX.Y`. The output of the command should resemble the + following: + + ```shell + bin cuda cuda-11 cuda-11.4 etc games include lib man sbin share src + ``` + + In this case, the directory is `cuda-11.4`. + 2. Add the CUDA toolkit path to your environment by appending the following + line to your `~/.bashrc` file. Replace `[DIR]` with the CUDA directory that + you discovered in the previous step. + + ```shell + echo "export PATH=/usr/local/[DIR]/bin${PATH:+:${PATH}}" >> ~/.bashrc + ``` + + 3. Run `source ~/.bashrc` to activate the new environment search path + +## 4. Install build tools + +Run the following command to install the Cmake tool and the pip tool. This step +might take a few minutes to complete. + +```shell +sudo apt install cmake && sudo apt install pip +``` + + +## 5. Create a GPU-enabled version of qsim + +1. Run the following command to clone the qsim repository. + + ```shell + git clone https://github.com/quantumlib/qsim.git + ``` + +2. Run `cd qsim`to change your working directory to qsim. +3. Run `make`to compile qsim. When CUDA toolkit is installed during a qsim + compilation, make builds the GPU version automatically. +4. Run `pip install .` to install your local version of qsimcirq. +5. Run `python3 -c "import qsimcirq; print(qsimcirq.qsim_gpu)"`to verify your + installation. If the installation completed successfully, the output from + the command should resemble the following: + + ```shell + + ``` + + +## 6. Verify your installation + +You can use the code below to verify that qsim uses your GPU. You can paste the +code directly into the REPL, or paste the code in a file and ask the Python +interpreter to run it. If you paste the code into a file, use the following +command in the interpreter to run it. Replace `[FILE]` with the name of the file +that you create. + +``` +>>> exec(open("[FILE]").read()) +``` + +In the `qsim` directory, run `python3` to start the Python REPL. Run the +following code. + +``` +# Import Cirq and qsim +import cirq +import qsimcirq + +# Instantiate qubits and create a circuit +q0, q1 = cirq.LineQubit.range(2) +circuit = cirq.Circuit(cirq.H(q0), cirq.CX(q0, q1)) + +# Instantiate a simulator that uses the GPU +gpu_options = qsimcirq.QSimOptions(use_gpu=True) +qsim_simulator = qsimcirq.QSimSimulator(qsim_options=gpu_options) + +# Run the simulation +print("Running simulation for the following circuit:") +print(circuit) + +qsim_results = qsim_simulator.compute_amplitudes( + circuit, bitstrings=[0b00, 0b01]) + +print("qsim results:") +print(qsim_results) +``` + +After a moment, you should see a result that looks similar to the following. + +``` +[(0.7071067690849304+0j), 0j] +``` + +## Clean up + +After you finish either tutorial, you can avoid continued billing by stopping or +deleting the VM instance that you create; visit the +[Compute Instances dashboard](https://pantheon.corp.google.com/compute/instances) +to manage your VM. + +For more information about managing your VM, see the following documentation +from Google Cloud: + +* [Stopping and starting a VM](https://cloud.google.com/compute/docs/instances/stop-start-instance) +* [Suspending and resuming an instance](https://cloud.google.com/compute/docs/instances/suspend-resume-instance) +* [Deleting a VM instance](https://cloud.google.com/compute/docs/instances/deleting-instance) + +## Next steps + +After you finish, don't forget to stop or delete your VM on the Compute +Instances dashboard. For more information, see the +[Clean Up section in the Overview](https://quantumai.google/qsim/tutorials/gcp_overview#clean_up). + +You are now ready to run your own large simulations on Google Cloud. For sample +code of a large circuit, see the [Simulate a large +circuit](https://quantumai.google/qsim/tutorials/q32d14) example. From 43170e12934d01969cf51910a932b504f91bcd07 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Thu, 30 Sep 2021 18:58:54 +0200 Subject: [PATCH 100/246] Add qsim_qtrajectory_cuda usage description. --- apps/qsim_qtrajectory_cuda.cu | 7 ++++++- docs/usage.md | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/apps/qsim_qtrajectory_cuda.cu b/apps/qsim_qtrajectory_cuda.cu index f5636c0f..ab9f122a 100644 --- a/apps/qsim_qtrajectory_cuda.cu +++ b/apps/qsim_qtrajectory_cuda.cu @@ -229,6 +229,11 @@ int main(int argc, char* argv[]) { return 1; } + if (opt.times.size() == 1 + && opt.times[0] == std::numeric_limits::max()) { + opt.times[0] = circuit.gates.back().time; + } + StateSpace::Parameter param1; Simulator::Parameter param2; Factory factory(param1, param2); @@ -256,7 +261,7 @@ int main(int argc, char* argv[]) { auto observables = GetObservables>(circuit.num_qubits); std::vector>>> results; - results.reserve(noisy_circuits.size()); + results.reserve(opt.num_trajectories); for (unsigned i = 0; i < opt.num_trajectories; ++i) { results.push_back({}); diff --git a/docs/usage.md b/docs/usage.md index 06b8712e..a0992fe2 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -91,6 +91,39 @@ Example: ./qsim_amplitudes.x -c ../circuits/circuit_q24 -t 4 -d 16,24 -i ../circuits/bitstrings_q24_s1,../circuits/bitstrings_q24_s2 -o ampl_q24_s1,ampl_q24_s2 -v 1 ``` +## qsim_qtrajectory_cuda usage + +``` +./qsim_qtrajectory_cuda.x -c circuit_file \ + -d times_to_calculate_observables \ + -a amplitude_damping_const \ + -p phase_damping_const \ + -t traj0 -n num_trajectories \ + -f max_fused_size \ + -v verbosity +``` + +| Flag | Description | +|-------|------------| +|`-c circuit_file` | circuit file to run| +|`-d times_to_calculate_observables` | comma-separated list of circuit times to calculate observables at| +|`-a amplitude_damping_const` | amplitude damping constant | +|`-p phase_damping_const` | phase damping constant | +|`-t traj0` | starting trajectory | +|`-n num_trajectories ` | number of trajectories to run starting with `traj0` | +|`-f max_fused_size` | maximum fused gate size| +|`-v verbosity` | verbosity level (0,1)| + +qsim_qtrajectory_cuda runs on GPUs. qsim_qtrajectory_cuda performs quantum +trajactory simulations with amplitude damping and phase damping noise channels. +qsim_qtrajectory_cuda calculates observables (operator X at each qubit) at +specified times. + +Example: +``` +./qsim_qtrajectory_cuda.x -c ../circuits/circuit_q24 -d 8,16,32 -a 0.005 -p 0.005 -t 0 -n 100 -f 4 -v 0 +``` + ## qsimh_base usage ``` From 2c7e8cf9be5a462b3381faa23f19bf2715aa6c80 Mon Sep 17 00:00:00 2001 From: Jae Yoo Date: Thu, 30 Sep 2021 18:12:41 +0000 Subject: [PATCH 101/246] Add fp_type and ApplyFusedGate test for MPSSimulator --- .vscode/settings.json | 70 +++++++++++++++++++++++++++++++++++++ lib/mps_simulator.h | 5 +-- lib/mps_statespace.h | 3 +- tests/mps_simulator_test.cc | 48 +++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..dc06896c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,70 @@ +{ + "files.associations": { + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "hash_map": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "ostream": "cpp", + "ranges": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp", + "valarray": "cpp" + } +} \ No newline at end of file diff --git a/lib/mps_simulator.h b/lib/mps_simulator.h index ae053690..98d00310 100644 --- a/lib/mps_simulator.h +++ b/lib/mps_simulator.h @@ -35,11 +35,12 @@ namespace mps { /** * Truncated Matrix Product State (MPS) circuit simulator w/ vectorization. */ -template +template class MPSSimulator final { public: - using MPSStateSpace_ = MPSStateSpace; + using MPSStateSpace_ = MPSStateSpace; using State = typename MPSStateSpace_::MPS; + using fp_type = typename MPSStateSpace_::fp_type; using Complex = std::complex; using Matrix = diff --git a/lib/mps_statespace.h b/lib/mps_statespace.h index 0520ef9f..26cfe190 100644 --- a/lib/mps_statespace.h +++ b/lib/mps_statespace.h @@ -51,10 +51,11 @@ inline void free(void* ptr) { * Class containing context and routines for fixed bond dimension * truncated Matrix Product State (MPS) simulation. */ -template +template class MPSStateSpace { private: public: + using fp_type = FP; using Pointer = std::unique_ptr; using Complex = std::complex; diff --git a/tests/mps_simulator_test.cc b/tests/mps_simulator_test.cc index 98092fd3..f70e63de 100644 --- a/tests/mps_simulator_test.cc +++ b/tests/mps_simulator_test.cc @@ -17,6 +17,7 @@ #include "../lib/formux.h" #include "../lib/gate_appl.h" #include "../lib/gates_cirq.h" +#include "../lib/gates_qsim.h" #include "gtest/gtest.h" namespace qsim { @@ -265,6 +266,53 @@ TEST(MPSSimulator, Apply1InteriorArbitrary) { ASSERT_NEAR(state.get()[l_offset + 63], 153.6, 1e-5); } +TEST(MPSSimulator, ApplyLeftFusedGate) { + // Apply a fused gate matrix to the first two qubits. + // Compute the state vector of: + // | | | + // +-+-----+-+ | + // |FusedGate| | + // +-+-----+-+ | + // | | | + // +-+-+ +-+-+ +-+-+ + // | 0 +-+ 1 +-+ 2 | + // +---+ +---+ +---+ + auto sim = MPSSimulator(1); + using MPSStateSpace = MPSSimulator::MPSStateSpace_; + auto ss = MPSStateSpace(1); + + auto gate1 = GateCZ::Create(2, 0, 1); + auto gate2 = GateHd::Create(0, 0); + auto gate3 = GateHd::Create(0, 1); + + GateFused> fgate1{kGateCZ, 2, {0, 1}, &gate1, {&gate2, &gate3}}; + auto mps = ss.CreateMPS(3, 2); + ss.SetMPSZero(mps); + ApplyFusedGate(sim, fgate1, mps); + + // float wf[32]; + // ss.ToWaveFunction(mps, wf); + // for (int i = 0; i < 16; i++) { + // std::cerr << wf[i] << std::endl; + // } + // EXPECT_NEAR(wf[0], 100, 1e-4); + // EXPECT_NEAR(wf[1], 0.2143698948599676, 1e-4); + // EXPECT_NEAR(wf[2], -0.11738977370009586, 1e-4); + // EXPECT_NEAR(wf[3], 0.12454935773362025, 1e-4); + // EXPECT_NEAR(wf[4], -0.47150921436100324, 1e-4); + // EXPECT_NEAR(wf[5], 0.2765580906536361, 1e-4); + // EXPECT_NEAR(wf[6], -0.00488996377601076, 1e-4); + // EXPECT_NEAR(wf[7], -0.052702222730185794, 1e-4); + // EXPECT_NEAR(wf[8], -0.15375504635790666, 1e-4); + // EXPECT_NEAR(wf[9], -0.006893208509005258, 1e-4); + // EXPECT_NEAR(wf[10], 0.3625325032891465, 1e-4); + // EXPECT_NEAR(wf[11], -0.12932957404571366, 1e-4); + // EXPECT_NEAR(wf[12], -0.3459372670125525, 1e-4); + // EXPECT_NEAR(wf[13], 0.4020253863039828, 1e-4); + // EXPECT_NEAR(wf[14], -0.21864624250067458, 1e-4); + // EXPECT_NEAR(wf[15], -0.16468189647310463, 1e-4); +} + TEST(MPSSimulator, Apply2Left01) { // Compute the state vector of: // | | | From 089101cbdb372c61fc7d557b278a355e4e2d5c2c Mon Sep 17 00:00:00 2001 From: Jae Yoo Date: Thu, 30 Sep 2021 19:09:02 +0000 Subject: [PATCH 102/246] Add MPSSimulator ApplyFusedGate{L,R,M} tests --- tests/mps_simulator_test.cc | 151 +++++++++++++++++++++++++----------- 1 file changed, 104 insertions(+), 47 deletions(-) diff --git a/tests/mps_simulator_test.cc b/tests/mps_simulator_test.cc index 589a627f..605a29c0 100644 --- a/tests/mps_simulator_test.cc +++ b/tests/mps_simulator_test.cc @@ -266,53 +266,6 @@ TEST(MPSSimulator, Apply1InteriorArbitrary) { ASSERT_NEAR(state.get()[l_offset + 63], 153.6, 1e-5); } -TEST(MPSSimulator, ApplyLeftFusedGate) { - // Apply a fused gate matrix to the first two qubits. - // Compute the state vector of: - // | | | - // +-+-----+-+ | - // |FusedGate| | - // +-+-----+-+ | - // | | | - // +-+-+ +-+-+ +-+-+ - // | 0 +-+ 1 +-+ 2 | - // +---+ +---+ +---+ - auto sim = MPSSimulator(1); - using MPSStateSpace = MPSSimulator::MPSStateSpace_; - auto ss = MPSStateSpace(1); - - auto gate1 = GateCZ::Create(2, 0, 1); - auto gate2 = GateHd::Create(0, 0); - auto gate3 = GateHd::Create(0, 1); - - GateFused> fgate1{kGateCZ, 2, {0, 1}, &gate1, {&gate2, &gate3}}; - auto mps = ss.CreateMPS(3, 2); - ss.SetMPSZero(mps); - ApplyFusedGate(sim, fgate1, mps); - - // float wf[32]; - // ss.ToWaveFunction(mps, wf); - // for (int i = 0; i < 16; i++) { - // std::cerr << wf[i] << std::endl; - // } - // EXPECT_NEAR(wf[0], 100, 1e-4); - // EXPECT_NEAR(wf[1], 0.2143698948599676, 1e-4); - // EXPECT_NEAR(wf[2], -0.11738977370009586, 1e-4); - // EXPECT_NEAR(wf[3], 0.12454935773362025, 1e-4); - // EXPECT_NEAR(wf[4], -0.47150921436100324, 1e-4); - // EXPECT_NEAR(wf[5], 0.2765580906536361, 1e-4); - // EXPECT_NEAR(wf[6], -0.00488996377601076, 1e-4); - // EXPECT_NEAR(wf[7], -0.052702222730185794, 1e-4); - // EXPECT_NEAR(wf[8], -0.15375504635790666, 1e-4); - // EXPECT_NEAR(wf[9], -0.006893208509005258, 1e-4); - // EXPECT_NEAR(wf[10], 0.3625325032891465, 1e-4); - // EXPECT_NEAR(wf[11], -0.12932957404571366, 1e-4); - // EXPECT_NEAR(wf[12], -0.3459372670125525, 1e-4); - // EXPECT_NEAR(wf[13], 0.4020253863039828, 1e-4); - // EXPECT_NEAR(wf[14], -0.21864624250067458, 1e-4); - // EXPECT_NEAR(wf[15], -0.16468189647310463, 1e-4); -} - TEST(MPSSimulator, Apply2Left01) { // Compute the state vector of: // | | | @@ -850,6 +803,110 @@ TEST(MPSSimulator, OneTwoQubitFuzz) { */ } +TEST(MPSSimulator, ApplyFusedGateLeft) { + // Apply a fused gate matrix to the first two qubits. + // Compute the state vector of: + // | | | + // +-+-----+-+ | + // |FusedGate| | + // +-+-----+-+ | + // | | | + // +-+-+ +-+-+ +-+-+ + // | 0 +-+ 1 +-+ 2 | + // +---+ +---+ +---+ + auto sim = MPSSimulator(1); + using MPSStateSpace = MPSSimulator::MPSStateSpace_; + auto ss = MPSStateSpace(1); + + auto gate1 = GateCZ::Create(2, 0, 1); + auto gate2 = GateHd::Create(0, 0); + auto gate3 = GateHd::Create(0, 1); + + GateFused> fgate1{kGateCZ, 2, {0, 1}, &gate1, + {&gate2, &gate3}}; + auto mps = ss.Create(3, 4); + ss.SetStateZero(mps); + ApplyFusedGate(sim, fgate1, mps); + + float wf[32]; + float ground_truth[] = {0.5, 0., 0., 0., 0.5, 0., 0., 0., + 0.5, 0., 0., 0., 0.5, 0., 0., 0.}; + ss.ToWaveFunction(mps, wf); + for (int i = 0; i < 16; i++) { + EXPECT_NEAR(wf[i], ground_truth[i], 1e-4); + } +} + +TEST(MPSSimulator, ApplyFusedGateRight) { + // Apply a fused gate matrix to the last two qubits. + // Compute the state vector of: + // | | | + // | +-+-----+-+ + // | |FusedGate| + // | +-+-----+-+ + // | | | + // +-+-+ +-+-+ +-+-+ + // | 0 +-+ 1 +-+ 2 | + // +---+ +---+ +---+ + auto sim = MPSSimulator(1); + using MPSStateSpace = MPSSimulator::MPSStateSpace_; + auto ss = MPSStateSpace(1); + + auto gate1 = GateCZ::Create(2, 1, 2); + auto gate2 = GateHd::Create(0, 1); + auto gate3 = GateHd::Create(0, 2); + + GateFused> fgate1{kGateCZ, 2, {1, 2}, &gate1, + {&gate2, &gate3}}; + auto mps = ss.Create(3, 4); + ss.SetStateZero(mps); + ApplyFusedGate(sim, fgate1, mps); + + float wf[32]; + float ground_truth[] = {0.5, 0., 0.5, 0., 0.5, 0., 0.5, 0., + 0., 0., 0., 0., 0., 0., 0., 0.}; + ss.ToWaveFunction(mps, wf); + for (int i = 0; i < 16; i++) { + EXPECT_NEAR(wf[i], ground_truth[i], 1e-4); + } +} + +TEST(MPSSimulator, ApplyFusedGateMiddle) { + // Apply a fused gate matrix to the middle two qubits. + // Compute the state vector of: + // | | | | + // | +-+-----+-+ | + // | |FusedGate| | + // | +-+-----+-+ | + // | | | | + // +-+-+ +-+-+ +-+-+ +-+-+ + // | 0 +-+ 1 +-+ 2 |-| 3 | + // +---+ +---+ +---+ +-+-+ + auto sim = MPSSimulator(1); + using MPSStateSpace = MPSSimulator::MPSStateSpace_; + auto ss = MPSStateSpace(1); + + auto gate1 = GateCZ::Create(2, 1, 2); + auto gate2 = GateHd::Create(0, 1); + auto gate3 = GateHd::Create(0, 2); + + GateFused> fgate1{kGateCZ, 2, {1, 2}, &gate1, + {&gate2, &gate3}}; + auto mps = ss.Create(4, 4); + ss.SetStateZero(mps); + ApplyFusedGate(sim, fgate1, mps); + + float wf[64]; + float ground_truth[] = {0.5, 0., 0., 0., 0.5, 0., 0., 0., + 0.5, 0., 0., 0., 0.5, 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0.}; + ss.ToWaveFunction(mps, wf); + for (int i = 0; i < 32; i++) { + EXPECT_NEAR(wf[i], ground_truth[i], 1e-4); + } +} + } // namespace } // namespace mps } // namespace qsim From 016af85eeef472f4dc8f69a4e410584e2d429e68 Mon Sep 17 00:00:00 2001 From: Jae Yoo Date: Thu, 30 Sep 2021 19:17:45 +0000 Subject: [PATCH 103/246] Fix BUILD and remove .vscode/settings.json --- .vscode/settings.json | 70 ------------------------------------------- tests/BUILD | 1 + 2 files changed, 1 insertion(+), 70 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index dc06896c..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "files.associations": { - "array": "cpp", - "atomic": "cpp", - "bit": "cpp", - "*.tcc": "cpp", - "cctype": "cpp", - "chrono": "cpp", - "clocale": "cpp", - "cmath": "cpp", - "compare": "cpp", - "complex": "cpp", - "concepts": "cpp", - "condition_variable": "cpp", - "cstdarg": "cpp", - "cstddef": "cpp", - "cstdint": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "cstring": "cpp", - "ctime": "cpp", - "cwchar": "cpp", - "cwctype": "cpp", - "deque": "cpp", - "forward_list": "cpp", - "list": "cpp", - "map": "cpp", - "set": "cpp", - "unordered_map": "cpp", - "unordered_set": "cpp", - "vector": "cpp", - "exception": "cpp", - "algorithm": "cpp", - "functional": "cpp", - "iterator": "cpp", - "memory": "cpp", - "memory_resource": "cpp", - "numeric": "cpp", - "optional": "cpp", - "random": "cpp", - "ratio": "cpp", - "string": "cpp", - "string_view": "cpp", - "system_error": "cpp", - "tuple": "cpp", - "type_traits": "cpp", - "utility": "cpp", - "hash_map": "cpp", - "fstream": "cpp", - "future": "cpp", - "initializer_list": "cpp", - "iomanip": "cpp", - "iosfwd": "cpp", - "iostream": "cpp", - "istream": "cpp", - "limits": "cpp", - "mutex": "cpp", - "new": "cpp", - "ostream": "cpp", - "ranges": "cpp", - "sstream": "cpp", - "stdexcept": "cpp", - "stop_token": "cpp", - "streambuf": "cpp", - "thread": "cpp", - "cinttypes": "cpp", - "typeinfo": "cpp", - "valarray": "cpp" - } -} \ No newline at end of file diff --git a/tests/BUILD b/tests/BUILD index 53f72ab4..47f90535 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -613,6 +613,7 @@ cc_test( "@com_google_googletest//:gtest_main", "//lib:gate_appl", "//lib:gates_cirq", + "//lib:gates_qsim", "//lib:mps_simulator", "//lib:formux", ], From 9e4d79044546caec471cefe4b7182533d8265f9e Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Thu, 30 Sep 2021 22:42:34 +0200 Subject: [PATCH 104/246] Fix memory leak. --- lib/simulator_cuda.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/simulator_cuda.h b/lib/simulator_cuda.h index 36b1e869..b507a224 100644 --- a/lib/simulator_cuda.h +++ b/lib/simulator_cuda.h @@ -60,6 +60,10 @@ class SimulatorCUDA final { ErrorCheck(cudaFree(d_idx)); ErrorCheck(cudaFree(d_ms)); ErrorCheck(cudaFree(d_xss)); + + if (scratch_ != nullptr) { + ErrorCheck(cudaFree(scratch_)); + } } /** From 42f5688ed82687d549e7fd0735616512cfcb6290 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 1 Oct 2021 07:57:49 -0700 Subject: [PATCH 105/246] Valid GPU, broken CPU --- CMakeLists.txt | 3 ++- pybind_interface/cuda/CMakeLists.txt | 15 +++++++++++++-- pybind_interface/decide/CMakeLists.txt | 15 +++++++++++++-- setup.py | 2 ++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d631366c..8aa448e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,9 @@ cmake_minimum_required(VERSION 3.11) -project(qsim) +project(qsim LANGUAGES CXX CUDA) ADD_SUBDIRECTORY(pybind_interface/sse) ADD_SUBDIRECTORY(pybind_interface/avx512) ADD_SUBDIRECTORY(pybind_interface/avx2) ADD_SUBDIRECTORY(pybind_interface/basic) +ADD_SUBDIRECTORY(pybind_interface/cuda) ADD_SUBDIRECTORY(pybind_interface/decide) diff --git a/pybind_interface/cuda/CMakeLists.txt b/pybind_interface/cuda/CMakeLists.txt index 75e23fbb..d7f5b836 100644 --- a/pybind_interface/cuda/CMakeLists.txt +++ b/pybind_interface/cuda/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.11) -project(qsim) +project(qsim LANGUAGES CXX CUDA) IF (WIN32) set(CMAKE_CXX_FLAGS "/O2 /openmp") @@ -26,4 +26,15 @@ if(NOT pybind11_POPULATED) FetchContent_Populate(pybind11) add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) endif() -pybind11_add_module(qsim_cuda pybind_main_cuda.cpp) + +find_package(PythonLibs 3.6 REQUIRED) +find_package(CUDA REQUIRED) + +include_directories(${PYTHON_INCLUDE_DIRS} ${pybind11_SOURCE_DIR}/include) + +cuda_add_library(qsim_cuda MODULE pybind_main_cuda.cpp) +set_target_properties(qsim_cuda PROPERTIES + PREFIX "${PYTHON_MODULE_PREFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}" +) +set_source_files_properties(pybind_main_cuda.cpp PROPERTIES LANGUAGE CUDA) diff --git a/pybind_interface/decide/CMakeLists.txt b/pybind_interface/decide/CMakeLists.txt index b7d28714..2c54f1cb 100644 --- a/pybind_interface/decide/CMakeLists.txt +++ b/pybind_interface/decide/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.11) -project(qsim) +project(qsim LANGUAGES CXX CUDA) IF (WIN32) set(CMAKE_CXX_FLAGS "/O2 /openmp") @@ -25,4 +25,15 @@ if(NOT pybind11_POPULATED) FetchContent_Populate(pybind11) add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) endif() -pybind11_add_module(qsim_decide decide.cpp) + +find_package(PythonLibs 3.6 REQUIRED) +find_package(CUDA REQUIRED) + +include_directories(${PYTHON_INCLUDE_DIRS} ${pybind11_SOURCE_DIR}/include) + +cuda_add_library(qsim_decide MODULE decide.cpp) +set_target_properties(qsim_decide PROPERTIES + PREFIX "${PYTHON_MODULE_PREFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}" +) +set_source_files_properties(decide.cpp PROPERTIES LANGUAGE CUDA) diff --git a/setup.py b/setup.py index 9fea30da..a6d76835 100644 --- a/setup.py +++ b/setup.py @@ -38,6 +38,7 @@ def run(self): def build_extension(self, ext): extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) cmake_args = [ + "-DCMAKE_CUDA_COMPILER=nvcc", "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=" + extdir, "-DPYTHON_EXECUTABLE=" + sys.executable, ] @@ -102,6 +103,7 @@ def build_extension(self, ext): CMakeExtension("qsimcirq/qsim_avx2"), CMakeExtension("qsimcirq/qsim_sse"), CMakeExtension("qsimcirq/qsim_basic"), + CMakeExtension("qsimcirq/qsim_cuda"), CMakeExtension("qsimcirq/qsim_decide"), ], cmdclass=dict(build_ext=CMakeBuild), From 3b540b61b12317273bd05ec76b92c0d9c554757c Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 1 Oct 2021 08:27:05 -0700 Subject: [PATCH 106/246] Valid CPU+GPU, Linux --- CMakeLists.txt | 10 +++++++-- pybind_interface/decide/CMakeLists.txt | 30 +++++++++++++++++--------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8aa448e3..d1a6c0ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,15 @@ cmake_minimum_required(VERSION 3.11) -project(qsim LANGUAGES CXX CUDA) + +execute_process(COMMAND which nvcc OUTPUT_VARIABLE has_nvcc) +if(has_nvcc STREQUAL "") + project(qsim) +else() + project(qsim LANGUAGES CXX CUDA) + ADD_SUBDIRECTORY(pybind_interface/cuda) +endif() ADD_SUBDIRECTORY(pybind_interface/sse) ADD_SUBDIRECTORY(pybind_interface/avx512) ADD_SUBDIRECTORY(pybind_interface/avx2) ADD_SUBDIRECTORY(pybind_interface/basic) -ADD_SUBDIRECTORY(pybind_interface/cuda) ADD_SUBDIRECTORY(pybind_interface/decide) diff --git a/pybind_interface/decide/CMakeLists.txt b/pybind_interface/decide/CMakeLists.txt index 2c54f1cb..3e9fcbb2 100644 --- a/pybind_interface/decide/CMakeLists.txt +++ b/pybind_interface/decide/CMakeLists.txt @@ -1,5 +1,11 @@ cmake_minimum_required(VERSION 3.11) -project(qsim LANGUAGES CXX CUDA) + +execute_process(COMMAND which nvcc OUTPUT_VARIABLE has_nvcc) +if(has_nvcc STREQUAL "") + project(qsim) +else() + project(qsim LANGUAGES CXX CUDA) +endif() IF (WIN32) set(CMAKE_CXX_FLAGS "/O2 /openmp") @@ -26,14 +32,18 @@ if(NOT pybind11_POPULATED) add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) endif() -find_package(PythonLibs 3.6 REQUIRED) -find_package(CUDA REQUIRED) +if(has_nvcc STREQUAL "") + pybind11_add_module(qsim_decide decide.cpp) +else() + find_package(PythonLibs 3.6 REQUIRED) + find_package(CUDA REQUIRED) -include_directories(${PYTHON_INCLUDE_DIRS} ${pybind11_SOURCE_DIR}/include) + include_directories(${PYTHON_INCLUDE_DIRS} ${pybind11_SOURCE_DIR}/include) -cuda_add_library(qsim_decide MODULE decide.cpp) -set_target_properties(qsim_decide PROPERTIES - PREFIX "${PYTHON_MODULE_PREFIX}" - SUFFIX "${PYTHON_MODULE_EXTENSION}" -) -set_source_files_properties(decide.cpp PROPERTIES LANGUAGE CUDA) + cuda_add_library(qsim_decide MODULE decide.cpp) + set_target_properties(qsim_decide PROPERTIES + PREFIX "${PYTHON_MODULE_PREFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}" + ) + set_source_files_properties(decide.cpp PROPERTIES LANGUAGE CUDA) +endif() From b543110dbce04f89ef1521fba465f438a86492d0 Mon Sep 17 00:00:00 2001 From: Sergei Isakov Date: Mon, 4 Oct 2021 16:32:46 +0200 Subject: [PATCH 107/246] Fix ASAN error. --- tests/simulator_testfixture.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/simulator_testfixture.h b/tests/simulator_testfixture.h index 17b571ec..717ff47d 100644 --- a/tests/simulator_testfixture.h +++ b/tests/simulator_testfixture.h @@ -1224,13 +1224,10 @@ void TestControlledGates(const Factory& factory, bool high_precision) { std::vector matrix; matrix.reserve(1 << (2 * max_target_qubits + 1)); - std::vector vec; - vec.reserve(state_space.MinSize(max_qubits)); + std::vector vec(state_space.MinSize(max_qubits)); // Iterate over circuit size. for (unsigned num_qubits = 2; num_qubits <= max_qubits; ++num_qubits) { - vec.resize(state_space.MinSize(num_qubits)); - unsigned size = 1 << num_qubits; unsigned nmask = size - 1; From e566ec807506afc10465689cb097aa5d22808216 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 4 Oct 2021 10:32:22 -0700 Subject: [PATCH 108/246] max_fused_gate_size recommendations --- qsimcirq/qsim_simulator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index a7b52335..91d93849 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -126,8 +126,9 @@ class QSimOptions: Args: max_fused_gate_size: maximum number of qubits allowed per fused gate. - Depending on the capabilities of the device qsim runs on, this - usually has best performance when set to 3 or 4. + For circuits of less than 22 qubits, set this to 2 or 3 for peak + performance. Larger circuits (with >= 22 qubits) perform best + with this set to 3 or 4. cpu_threads: number of threads to use when running on CPU. For best performance, this should equal the number of cores on the device. ev_noisy_repetitions: number of repetitions used for estimating From 9161bf3c287cd7ab93e40739bb125bbde91aa1d4 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 5 Oct 2021 07:24:49 -0700 Subject: [PATCH 109/246] account for complexity --- qsimcirq/qsim_simulator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qsimcirq/qsim_simulator.py b/qsimcirq/qsim_simulator.py index 91d93849..8f278a2f 100644 --- a/qsimcirq/qsim_simulator.py +++ b/qsimcirq/qsim_simulator.py @@ -126,9 +126,9 @@ class QSimOptions: Args: max_fused_gate_size: maximum number of qubits allowed per fused gate. - For circuits of less than 22 qubits, set this to 2 or 3 for peak - performance. Larger circuits (with >= 22 qubits) perform best - with this set to 3 or 4. + Circuits of less than 22 qubits usually perform best with this set + to 2 or 3, while larger circuits (with >= 22 qubits) typically + perform better with it set to 3 or 4. cpu_threads: number of threads to use when running on CPU. For best performance, this should equal the number of cores on the device. ev_noisy_repetitions: number of repetitions used for estimating From 4c1a99c7a6979e6e0758a0a6d863813a218fb0a8 Mon Sep 17 00:00:00 2001 From: Ricardo Olenewa Date: Tue, 5 Oct 2021 17:47:02 +0000 Subject: [PATCH 110/246] Update to address comments from 95-martin-orion. --- docs/images/colab_connect.png | Bin 75964 -> 69144 bytes docs/images/colab_connected.png | Bin 32211 -> 30086 bytes docs/tutorials/gcp_before_you_begin.md | 1 + docs/tutorials/gcp_cpu.md | 58 ++++++------ docs/tutorials/gcp_gpu.md | 119 +++++++++++-------------- 5 files changed, 80 insertions(+), 98 deletions(-) diff --git a/docs/images/colab_connect.png b/docs/images/colab_connect.png index 9c74edf91dfa433c6ccb1bd49ceb1c20dfc70b9f..1a52c18f4a0ced28b6b1bccc5c1b33dbf71c9cce 100644 GIT binary patch literal 69144 zcmeFYWmKF?(>4kO5?n*DKyY{W;K5x7*WfOLh2Rn-3>G}NyL)hV7-W#(?rzDM?B{vE z?4A9df9KCx>w|mN^mKQ%-Bn#zRab{AE51WRCPapTfkBgzmQaO(dDaR816Pdr9GXMW zqKW_mgKA?XF0L#iE>5cK>|kMKYYqb=9iE`|LR)R{RfaC;^XCaTtk2k9IJsJMWVMKJ zst77bGNf5OUoh1Vhq41~@Kj27i^Bp+7)U=wge^-}-Sh@TR5R~q)6zSL!8IQGA?yT}A3d%uU7INXq>Y$|*O!5-Svxnw*0i;K`H_9* zr~b+^Ojvfue&P4~om)R#;&zd~XD~8>yn!qmTcS&-Fzw+qvD?o?)OVTkcjR6Ig?pM+ z@kx7ZaCzmzZ)hDuuzAoHDSN8O+EG6eCJZN7z8mj8y|QX>_i$;f{;!&v_mOUigAYR&&X+DUZcRzrMQJs zHXL8AL;s{x?mAJ?HTTR@kzB6#rxfk?!dtc|F=H|Io|j=vC=xQ0m^LDZl*8dM>I;!{ zFRODxZFA3gB_9(p`;mIqI%PkUlByBDDyU`|j6>V{WQP;MJ4x@uYWM{+s}ut~w1M7- z;nK;B(o@<)M&fTPHlxh!BPw0U?s3>8l7?Tgz8AOoqMvSO{iTe3=@1DI-ew5H zBn`njaugSYx#`aN&Vk6SwM2A7Ftkx3v__Yeej4VOJ|WsDBw4F`uOGi5@?sN1ZI*lk z0{9O8JpBx#?}<#ntdF3P%&})V>&+_?L?LexCSBZyZ2FOt#-E9uJU>H*q7UVz1}O$a z%^)Z5t>(Z?eD>}h++U= z++)F$k#Pn^C<&Yi8OvAvXiO5HCblg30O7~t2NO8E z2$%ee{_PUpY#)nEQAbc+dRp1IUj^owB!79C`YFN4T$@TWkgMzG9X;6J&Gd~Uxnh5E zBZXck7La_byI{@b%=}i$2g4w=VO{Gi6o~O7ScC!}>osCKJi0L)*(cU?WqDePL-NRK@uuo8Ebrl$YC=ALj@g=;sO>w&^{6yw|x3-G5Wa zukKBsnj)2;mZavPmRi(MxIK@0Al#|8E-6poKv~C`tSJni;1-z@Zx4yETuG6nIt-YvCvO1X+ zE_l)l%{48Z8!6fTu`|E+lX=@9}<$y%)WQd%}JK zJRRJ>LHqJN>p9=^64ZUPW1{tt>yQn!0wQ}hLGyVbRgc_N>6HOc?2C6RY+_M|QR|Tw z1V^mRmNPcCCa>)o*;b5vrWzqZ#@J>cyK3;}7nvlwWzKKvX3gNs#yymLlDOUx^F$8a zWFZSF3oHxgnj;H4i>Gbw?eJ}sQJc{dU1i2rCUzz;pjP`*I|UG3lV+!5=h+`&yJbgJ z3vh7j+i$Cja;v^FIq*8z;lAg7ohp`Uo$6iB2@!!<)hE=?9frBDxbwNE9x+35u1T)N z9*`g8& z2)^>J1^QfHp2vHEf$p91 zZ`~eUc?i5ZvBf-%J~jP-JPE*kf!%|J!12S4!ZrCP_{Rh=2Hc9a1Y}elYA=-f zmTGm5bxwWn?mSs&Kjb^y#$==v%%#j_Qm5f$b2Z%!WWA^?t=zB7aEv+CSsTR)3L6O1 zz~bTSdreXhG0g$CIZUf6i=7#`9f*pJ$v(`ZMLKUE@G4-(d zd2@D<7)MI!3&)yRgmDCI9$7+sN_;azorlS8j~kA_wBS_4w7qvfK0md|_}Ncx!W~59ilu-B~05b7VGoyyme6 zZ03gaEZDru9!s}Y<&s~3+}q~*A?-`rGT*bN!KOM-YtKE;cRl>;ubmFm*Zck z_JX3V+NZ$^2eDGJ9)p+*lse+=%z`t9@sJ_E&u>a^-G)r?xOe0g$2w;8)`8d!>KV!B}5#vMIKdn4>O4mT8#hS0&}YS8Gl zX7~w&$MuQ;Z6EoKJ6>&YZGdOW%*&Z{9_E_q4#pCi zlI--AbXW{mRY){eDm$IKhtM*)k$DU%5X%df&1$Xo@^4b_QY&nc7FBCzfi`>K7>ESK z-VS5cK+8&Br#`ST$EbfgT}EL1aq~%SmbqI$uSNLe)yeTZPD4T6P)V_M#pDX^tYuwm zvyI_m`|yMKHO~w$*tWuaCXoc@#pVlx$Fv_8QGu~9bkRNE6yNpkCn<#Ph05)JZccU+ zy6U=*4v7!BrZA1E<-F%2`7ygUx2#~Hfcx$$x|~;vPt(=*J8-M2lrBW^^ydUJP=l7Y9+?4>98nA-0U3K4|t zGkfY=Hy*k)Rr$(4R5AE#De9p$dEFE4ECwG;&Osa~R~p?ojx73_ew6JrxHxpQt2q|@ zJbv^J@}|0}J5CZ}^-{iTSw5*F&LDpGCPc9HmiEWwa(`<-OXfg^&pxAkDSCcLYE|2cMKdp`QZ^n$@7xO1a1>psP%8KC&tXm~*Ps7(Qm0+xm3V8c!Qek;vQm=dl(7b!G zgvk)U+S_+4P*uzGXi?$et^^_aK6~a61xv~WcdEnBI?>^2^!-8+|IiL5rGW0_D>7M^ zKzBVRLUWtNl+S58?McDv=B+QrORUDglLtRo;p|vZqz}G}Q0pVnTwBIMK>>yynnr{{ zfW?M+22H_2|6yPWVW5V|uM~_dEYbf-tHRR$w+&Cnz)P%^jpo;+1%XT#nQpGS{`E-nt|jf zt>Xd%^NQ-%2UbRv@)T-1%~`2yyJ{=Q^P4)@F&mpXn3yvI?Hqr#10x9JhbHaJU5!bB zcDD8|{6Hb{ziRM9)4#G=$VvaI;%XyAuC1UKHPRh;9%FIeGj7&;OD(Gxx!LKSI z^BoGp7yTBKqh+^ivKk7 zKkZ1EyO=s#Il5Xo*pvQh*Vx3t%~gn;{8vZ+=kGt`GzVJ!?#bTezte(FkmXki3mY>l z%l}5^YGv{NLiVfVKV*N+>p$HI{%VY0*$QZGt1V$=2VH9D(1iK8xCQ^}=3gbh2l^jM z4Ht7~aR)mnp{wxkclBT5zZd>V_*a)Yzq{n-W&eAZe=GSL<*zC5tD3tw*t-2%LJfN> zS79jG|CaqHmCkQ6VKz?A{}TN@_fHD#|CQqJxqnh9Ia@*3!T8tEgxUVv!{77%TVIgn z*Q@^p_inlQ2;%l~0oVPuuZpHDC_A}}%%qUu1{!}J$EuV=67$BSQO3jk3o9@JC#b-TsPMGzE%v$H2 zwDL3!S++484NX~VeZc6rIv!g}_TEX>VLM548Fz9kL5hNx{`lu3h40^pjWh=#b?fKV#w+E#_`=dJa=l`Yn|MyybV;1=~x5xEvVRn{j z`To3tIO(<_tnCD;ipMPa%bzSIP5K&6!#p+ba(IjV!=mlrz-ZD;t-RA+GnFuz=u%l( zwOu#flz|})0Uv^@;D?CZovqNCheA8KPNKSkkY^Nsu%Z7cQtgbEjEhEc1lVM7BB`fe z&RMp)mV0IhJpL9$hjN`16eO`w>m2OL$ukf#E}S@YM3gstln~T|N{25XlRu(Q>VnQc zkGOvN4pT^1`BZAW>AUowd}|~E-`vuY+ukZq5B@;wW^O|{wxf2ILT+_O1rl;EHp7dF z3s9j_Z8J}=ZI~IpR!GgYctS2Jn`a~f+r?TmtP$+adB}~{}^U5zfa?P zMyOP4E73{B^gdwYPwDbcC!LsFOGMX-aFwm@ajTb;Gfm(GgQeB(xbI5a^g$d`xn+5h z2vreF37K+w-Z|=;nlhccY@lQ3@_9?a8tSZIsRGi$lHU+`5sHx3j5S^MwTvVzol@OnS zAQ~D-OHflPijYdULTeodN1RZcku&>yRB%lX8|aVeeDg!Hy*+$J_jJm&cYG-$t686G z30K@)ms#>+j^U24X-ZjzTH)$y{pc{!Zi{Pu>L))xfQp$}^lcLrM=wozxnlU7^44WM zaKT`X7MF-;2;%1}c0GS0LidH$vjC)FHA!JrUV)|LQnMLD%;I#`O(PIm@!p{PACvxT z4UDl0^*U4~OO%lF#}dmjeaV?1^|AnK5O{VrkN#r=kGfoTBB8ZLRq~eNF?{r%*cDzEkB1Pqc>g**V4oR<4vB5q9fl`2%&`^swJ z@=RbVH)EbTJgM~1Rz^`SzIcc~y#hkU$CpgFzC@=_Y@lLi+uD%IP%i^eY^S8mlU212 zq0G%4T1;Tb^LltrH>4OBIih~H*S--qO{o*7o3|AOvhsdIP1lQKwza5w1*bLSxsy)g z>FEiJ(b8_wVypiYZhLp?w$LaWXBb5nJK4e#FNC4TD4Em$e2IDrg$_uekoFY&2M3CT z!TZpg%=w%a$NRObgG=>U3~+1H4buHeGG<#JQLi@hOZqiA*l2oWG_3L%VOwsh#(VW5O#WEs|mo#$a$EL0-7Os4OYC)ptBh-u94fExr`(`n-0vFat3DU zqFj){<0n=4 zvOOL1kR};~Dk`O@si;VQu~m4Gfj&NDQ>EIDqf5#C^?MY&`L*ajw;2(*K#8w%rirV*Yj0HR&r$ZQ zOXB-dWED&kO0&lFRZCsGeOH)^9Ey`hMmVj`Tq777l%oarDHn3rMu>P4hnAkMV;d3~ zW4SUvlx7&EKR~t;L2NmBH3Fh-U2apvNtEGtaprOgak-RKL!B4-(qaehbdGnq?ia6$ zZu*8dpQ00=DG9w3>Y1c-WwNF&tERkF{BwLNieFnw5uBJrZl%SlpIQC ztj_7*fT`*%u^BRJtw)zR%Q{n&V#&Ao@ov(25(k!=ydx-StguTsli6MJfhmBztTNG_ zIdY9zG*G+-?O@T}l&Ttq=>?(L_K1(u!M#cuk@+kQgLw^u4m$4vHs4ku4%a7_dg_`8 zXKj8#=p<>k?+C zR`wF%nQC=cXPKDnlXABjWD`P|cB1JMb1z-#NCXtu@m%HH6cTJ1Amm#Ib26X?S!$&e zM!E$Vb}PMh;9|;FoEt-4Ujj21ovGOmto~t9LjkQ}601_zNPGl`$TV8?xIZ}&J5J4P zXvwpB{c5Mq^|Q3TLer*=YVmCkyi?J4$%t*u^c~3F^yx;QCV_w_&w_58OqSs13J4W( zbGsS`J52$#tg~kErt6N3=87fV$WTH!LJ|La9z)@uu0`?X5Ynho5-a&t1@PRS;CF%= zv&6BjD5|PYCBXqn9R&XbyZ(g~jyvfdvXYXL-<+bRysB28jy;~06YZh+(rdqqBB8QT z1DOrq&w{>3Oq*#etk3>b2Dp%t*tT> z>5D6S{e5%y*AM&_fy=O&mt=tKRjKVaqw;Pr!+Sq%M!%?VIA+ThQG0v}TIOIq zOOo7HNL@aj4IiW=SerxsSpMo1cXIuMLF!c;sTFsO7l$_fM$j(O_SMl3a)C?y6>KHj6+F_ zc)bHl95-$cW?PlX0(!+JB(+w zM&YZWscF5c14>UPz6#r0i@I0qf&B;S`BpAz)E$EMMB+$mcfTxy$|+(}yg%eTIKToz z_eiBq!?vB?!z$*;V>ZxW&tVoTWR1 z>*j1$djpUqjtJ51dXKk9Q%ygT#A){S$gGq8b5SX=zCA91!I}$xKc5hg3e!UFnzF)l zYDIN_dbXZEw%@FHtLo`7etN~^e9TY%HV14M0Uj-@Fks*sWnw@0NRcOl^G?HFpFw6_ z6T?2owXa}c@_h!A%QeJsU8B|OmomDN)|JbNShpxDrcK%}wx*jfQ!8%-T3H``zC*zP zZEw3<6;@znR;X1^yKoBf>!+w=#$*aER7T8&cLwukdaU;#Nr`y38C=15l=L^MU~>jF zLqXyB`FVB_G!NyAW0pb_#62~sq;Oqb@}Yp0;cH4Hz*nV?~E z38Sa`s{quI%GJkh9;x_6cy;X`$1NuzoY;sEn~FBDZA|1S_hf?0!|Kl;H`NgPmf+eB zOZ)h=iL&fE`5GoAkYs;4J;REx0^p!QM%TB7lc&i%GbbiC8ReC%n02$OSL;-fOwnf* zs9KerXTc&UlqVMUPdr1gT5iK%S$sj~AF>-NaO4^rZSs9qp_ zRL35~GJhR}qlh?VJJlsWTATLPTq)V8~01RyVw~iaXxfWK9=YB#3U00X;Yg|txcP53wGy}xC2(})2L%~DV6j*9U>{#y1DJy2J4(b zi+RK8uki5Ri}hep`FG$lHUEr`M!ygkp5yDVnJdSAL|?NC#ry!(Z<~P_!mI{HkK95^UF?Z7RiJ*H-ozq>I!;At=qlX;-M!% z*0L6>kfb3(A5BgwJbRSzL#yY8g_VuFoQY3LZa!t$cmnxRx)UH~j-q6)6!e5HDB_b6 z%O`ViMqQ9A?Q9*<*v$2iFl3bJ<@JQt^(q5GpZ*qg?bBTgP$UGeFJu74CxnWcdgy(o zFZFN|3y;ADm0aS$xZe+#rsL)V@{EOKos_3>%8uX0v`8e*N1Cm9(-<_FL-b2!vfRlR z_k`Bs-1F~9JbPRNn=&v2Pd^^E-K_NK#>dB-b*N?cbQJwvZ{s-6{Oy}Z7I=xbf~mT zqdQT66AM?cI$VYhRFq+mNu4RHIIV1Pb)293UfRStVn{EcIh}Z)4$L_rjYj-UR#rAr zt3f)>a3NO%(dEZxoRYjHhhV36bo=Ld}eL``m5e90HUK*>UX7p5zhyS#`Ws_XtQ$ zrFP!Ym@@VKn$OGTM0E!K*>C{Y2dyS7jBGUDK3(`fMuZmBC* zNdkivv?}baWveM=18NH!z#C3PYiYS_Q_JndLap@&f-H`hS7)nGJDp_wT9eeY#VFuL$?ra{^|TA+{B9?c2;*f} zX6!9u)>LN?i#@pW`2|kZ^7Ro!LFSbiZ2KliZu7JoW7XL5)Rrvv)!}|g#VgABF7bdE z;roHG0Q2F*D{NysJG+1?p{L8~YO85#ibAYrv>5I&b4HrY0Fi)GwBCv9A%+T0levWz zWTP;H)V+l!;raK64wO2**LJFc?yZ|!c!f7$3Srz9%z6?tOK|1(=B5Obc@U;~lEL^O z|EiWDKOaw1SuY|49OAC?bBmv#Y~hPmxdD@%LKGpiKIc>YfY?Lf9}y``BM zbsPnM`MvL-Rtc>y*FE6YP*ftv!KWX0N(`T@dk`w*G(3#_>WB$efAiZ%c$0u{qA!Rk zzlOx_OCvs@5 zdaqyj##V=%lBG+p=Y57J_4jeRhW}!+p`p`QfPHX7SaK{Wmf-3;n|9?$yQu8lk7li7Pm{L}f}iuGTronW?PB@&CB zQ>4|jGpbL1a*2M5dU;~6ZQJ zOizEW&nR%~p`Ux*&{RH!vgg+l?B88MI=e`EP1?EIEZroD)X> ztO9(aSQGo3VdMXWtkQLF%3wu5$gwMxS3Lxqg7#TZkQOWAcZwK6XX*PMA5oww+YJWK z>}deGXEa(S>K6VRzegvRxad?Dvv{Gi9-OHDm&ut`en6YGD9N(PU=%#$F7EUYWoxta zV44G>)!UFI$8NOq^b?g+esh_xG8{H%y$5q}9*YkYa2|1H-Bf7DNu7F?EkxedTLL^pirYIwIP77ea2s zAx0DHbRWqv#(Dw2=I^^-RIs_56O*_REie}J@Ek=P-BoaaIqKBNX1YL$2ky4A8=b?r zucH0F_)KEq>R_&dM~rCIq2-8sxT>#U zOL!Wjmk5uS3Ni9PuI(&hi4aJ0Zdo@C%C$aaPG4kF{F2I4K2nAIs!i5N+CyeokVqP< z5?+R`UIAu1aQ-8u#+M`k<1Tod^_b9iz%aA6W+47^r%2m{n)lM%TUw6hV>wqbRvmxBeb$E**idIFcfZCeVtR;ajF3Fo3aGgX+(6ri@;x7< zL0|z!w4Wzb3^eUzK!#fgEg4S>`FqRl0$;$#(I|1R7*c+BB82}161{5>yTGe`kp!kg z{B1vfJP677JVL$;bX7eI*9UeeOCg0+zAu>PLb=L(x`rrSFWRu+97pErRWSmcI2*dn2zp;rabFC5fD_B)fwID6hL|!OvH9F`-2)k2FaPq6jZaw#yMWSCPhkas!o-n zxlB+-UNH~Ta}4Ie%f{;v@{wwXkuV~?d=r4@azy4#<~4~fQ;MnYj~JGfVu5woilBso zXN$s2^AD$jF)6FprKRU-=L-ax8ceW_LmHMUQU$GG`<#Jy8C^KGEzmbuWfAk#`YM_a zjpyPtWdm0pQ$CG%(iRRwo0Wl?8NCtN#hvV>QRr*uFi(t23^8Bh9|3~Oy39m6@QaYy zntdy9oZOtpFz8|pO-&2(y59u#5Uq)(BHg5o zf>&|7p3WXbh=PuDh~e2ug4VLow<{>T;HI(3tuFt>_GIMWQH}e2RzwWsU6+$?c;+YW z7oYp;)WFSrcvI{#vf6v_?(Kan-P=njH|QoZ>SQmx7dnT+!)eOV&ejD-4#Ev;l2;=+ zXe_fGKdx9dtU~ZmSQs3g#NR3h?hF%JCkQ{Dc3<06+?<~~Ul1=;u}KN67(|(DqKKsg z5^U4!I=v~4(vPn1EUgd@u)`@EUbX|A(+DfRiR8rZOikaJuH59t?IgLHw+?3*{gxJw zU%};p(^FFCrZ$aD)$mmrq#opMz3c2N9;dEllTC-)gCH5qf1=g>RX2rdY>v5vr!+34 z?`N25Ln3v6CGp!2t>0nM;F)+$j+q4OG!FxC%0)+_LcRr8_P$3n75v!6e)wXYNbUJ) z<&Xtirm&rt1RK;AddVa3+g0zMMfSZk{S&5X5c3P{V*l-RbXSQVYm=uRrKUdfqGXK# zMQ@k94$3S@4!uv>Xyy$4+8&_}Yr@*Kuc(tychzDXDH2J2YpWoK1J(-Th`EIHd)BI@ zIg!ub`xl`~*W&GS-_Xj?xzY}71yK*y5eCe5V%s0gAt8suSz{ZL5Tg)b(ivNwLZc%S zlco~gUi&ijgao-HC7~8VnU8>GV&aUSKyUrC{iCZwM?3YkWO@R=CVKa2O3{NuuNf4* z92FGdva(d3V(IRT4K7oc8epB7imFG%$O@C3FrY>hY{||OrirLQ86LPRxhp94h0L-{ z3vW)q;LGr*0eHO_?H8hvv`DsrcE*Svef5@~?2Me~mnf`Ww#Md;w*Nj%GzvpBVrCM9 z#!cwO8;d^Q$~tcrbIii9*%kQwt^OeNp4f#?W%bnx6)-*a2>;^6k;3)ZB?g=CSub8m zXer<`!L9e|L&j^L(@rVN`6m;5wjmL;o`ZeCcbG?=)-NFKrzGV!i%mz4z99vy@zoO2 zVz?2zlBq^WC5LRn=K~Z0xu#VHiPBf)GwSv{aMh$0BI{A#>^K5=-u0FZNn2UjXf>2b zTWBWcnp?=|iIL~u41q^=IdxqEBYwILf=@TDxf>`cX`tsc*Sv^I;aAwr>v+ww;J}!grf-FY4Y?Dg-pTQV$Wzk5{==o|1Of zAP4U3K3&NQy|&~GqnxwKx=g{6re=>lNU9a) z!6_y5R1CT}YPiwZ6<%JcnAxRSqXhf2iqg_xzP3SCT@8vY$nfa>W-FsF>9ie4pKOBz zTd#9&UnNUWJgz=3FYmy|2+a;W;VVxdQYbX!$z;$vZ)WNdVGuM&VN0;^Wlgf*;hESg z43qD(b=j}Fz+O>U*Kc1NZ@+APdJ=2Q`0Wrk34*bgXKDN%km1#enLDTNk?z^~uzyL( zkFnQBd%gU`Gk3jdQAucK8hV#Rm(*{lfRk^x7XUpC7{+LV z2;{cfws6j9$jLefcsb0<9UM(eY%8YUK&-0yyDt{=RySNBdpLbEC1WqHsq#~7hi=x8bp=wa>pVi$z4xaJW~hj@xk(WF5c6~F_k zUBLT&pn=K;3j~nauKBwGNVBZxHY~t|!U8xd3tOElYoh3hqzv=iYmA|mLTf=dn=F2J zpO9)K0_bN}(99YMLU~?2SHb~pp@uA+4o5+cep9$?+~-+_u*i~eZArWE*1rC7>F5`a1@h(n?eM-V5Ut{XatUC2gH(aER2B^oZSeGg##Sc#^!^n4NbK3e0%K9#x@ zaMAmIYHG$p!os^Hgk2nH4P-SiNFP49a@}uTalg9Fmr<2X5uKRQuGk8rpLv!_&$l0} z05;P^^uG2|=^H4>R)1^5M=^7xATK+^IJSpf(RDfNv6^(WPe4+E0c54Y%g}q&pRY6y zEIL7pVHQgw3n2r>HjY{l^q=5%z=i=3^$_(_cabw`tl`CTyU!>DOnE0}`^|rYCFlW~ zEDYS`!uw1vbJyo?{n6=UqO-aVZCtq~bMB!VRV3{vK05+`p>l4Al_d)1HDf@#Pt%z_ zBvsY&=(%L7zwc!we4bE{N7( z1HVh&y-K|!c=+W?Qixg!CGnEB?|8b-IO~~u5vJ%(LcwQ$$v1EO6ie6vS!O=y&}QyM zSl~b5)=A-#b9v|TshuY!SA)bO6?u&M%(5jKz2apw{mU~qX*Iiq+tyKbW4)|MVh-8f z56$(HmG7AZX1)@n9uPs#&f4`W*c9~g9u#D!Crp_<E; zysfq~-zMzkieAM_v|Fq#+5t%Na)^Cl!ywMaGa(4EAsSvIh_0RNCbC*TjQK60I9pj2ojG?+Bb{K|mtrYe^^)}k zH?9S}-ETNZWXc*ERQN8uA-RSg%Wg?BVZj@n zt>(Z)=zSf!W3~cZvfV1n(@LnHF(E%qow7eh*mSZ50tS<#acQW=XVPf!3uR^Hi;BlL z>~-VD#5+Lfna^b1%aL_r649-#F+FTP&1fVLjhATkIsl}Ko1_E;TJp+N+A8a!R7>FM z+p~1y45(zq^sG0ucv5)lJW;L9lHVpcHW#!2W5}?R0&$H+NTWcj|i}o)lI%wCh zOnm?@i(@En?7IAfxr3u5$acskG9t#?9T+7chlzuj6H=MUWs~^YYd35AcEX{QaNB<| z^jR$L^UGji(?NcLHdQTsy0R!YP#$@0_tBJRV_C{aSp)~o;14J2{*XeQ)D2ey`$Xgs zbe#r^w%U45*-u{H(`sDt+LDgNAtXGUwKBSK@2rchHNH0I>F!MD=jY4WYes{2?2?%G z;=9;#s0@2c8*pI~nmJ0-FBz-}Y~M@VgE7NK9|Q?@)NJGX8ispJ4#wMM9F{eb=Z)heMO^ffX> zklvcHKbHg_Z(P8_*5rLOUmdP_-c*)MX2hrd-8B95x4LtbCmk0Vt@n>Es_e$KFL5`m zEw(fVM~7qAt8(&Mt7TjDot!7KLQ8Jt)`Ffj8U4C zNYr%|EpW`rW$?i714LE9f%**!j63GvUVnP(2Q}%Y<~a`&Qpn=OarS8MEZDEKDkQQz ziL~Fk2Ai4X?K$B_D7950coKk$SAEaIt8c%U=1F(2t7sNlUuf#ItaB_>TYaLgu})z2 zm5$3uU@)bYpSfO8)iEeqS#wUWNhPI<>$71pNY7z71LbEd)y7LK6kmOSUZ7iMcbYPb z6GTIrA$26PWHZFWdQO{;Ko&WEdTK(vT}R+JFtG^~ynCnU0I)6}g^ zOOAjH1Pk>oF;Ms~CK>uLzfbqAxA0J8GtdiLK0iOQXlDsxUhFc>Oiy2)ql+BiM_vgt zvY`39LV~8Vus>=$hfu#*M7NVlNv2dv_Egop&ln4hiW@5Czv=^9-a4jpHl^l@AM7M* zCCG$Z7-R^$6f^RFhmny}07clPa!#s8S-daDQIacZ>_@EhW14H6qPh%V&00ecfjT`Z2sJOOGRodzhiD-MZX{sQuQ zWR#VFuY(1NEV}fm9gh;u$gcdIx8bVFf-2&V1q5fGO!0@5e>#ryAviSl%>NC;{bEle zX{6gwz8Cw9@!bCPpRUajg~pgjsJ0HcWv%rZEL0RhuN*)Yn&4_-DjY8V1rpV?q&$Ne zL4~hidh=9FM)_k z%v^B+uguU;iCG(UQJ3H<;A$Vr*pkki4|hL4p>xU z+n+5NeZ3r~$S=e07HwY;!e|P;+tZ-XyjpuHPghc$l-!~)gL#g9MZz;!jIo+%mnt{w zE%*)^O2Wm-uL4q29d+;aB%uX{#tA=A|J~66i#_iTv%Sr1(8{|jXh2zt%<1P5O5D2} z02*u=Y-A{(K^<@k0P#Oqk{1PJ3tE*qd_Go;-M+WbX)~#tQJ*Vj#g&;F8J5B*5b@gxe>ZN@)VNS9p5xIo`#c4_{H;qwM zK(mHm%ChHSqtm*oF(hwoG>X3hFKe9k3?Z<pp1AI#iy^6rhV89QfY8B#InXR;s_ zZ@fCUw)-|!gxgyrD$v)#l110o$wgMxDmHK}>)4b`9U^Y_0buMgmCiKi0ans+H-r5$ zv(8PG<76i`-rLKCR63x?mLXP5`ftGXOPolpkqVVGxP5*EduhZ`F>%Q719W?qr^t^+ z>75}$AR({F*Nrz~ce`Z`d88#G;xWEc`&M`rO3N+xvaBnsl*xQn0j}@0`N~_6yyzbqTGas z&G&{wg=%p)_0M?b`2-}h?d?M4tHF9S8%iM>NdQp_r;J8ad^vz(ncjls9?Yf6X;I3^ zYZTjhKeL+awd|E%keZbN7;Rt5=K^w-^O4aRJWG{)b<8GrJ(<#Wx^&M8anaPwke|74 zt+0H)!-Ts9jf=s?#x~pUkzvov?EyOWzlupboN;CHRHxyW(?;%(j0M^~6K~T;JWx%4 zCkWsW?uHy5ILZZ3*Q8{V?bT?|KIndmxI(`or+DU?JCbK_6%;)E55@aeoKJQn%;sXm zjX_d~9mA~LB5560*_85JzMI3a`B`$ounJAOiBzP!jj#^0VS9)Al3N|2`x4zKz-8{> zXr@}+ApI@FeFP8OV&293jo3*Y8h8&cu4gU<7AD}xiM!xCP*Q5yR%9!nx=?o*>rQ?9 zKDV+^1i_=(b$1C;iyB1JpL*9eu!x!}*eqSlZTHfl1sQPU@krvb^}Y99s$2IHhEQv_ z@ALV$f7{Xi+TGyEK1!&#;1Sc8tenJO-(yftZcSrL3d_s@x6CV;Vtvv1*47E`SC7qZBzE5K8gxQ7)sBt~tW?JtTHBAo z?oL*TJDdvgTj0;{W6G7L_}kN)88tYVJ&Ip6G!>~dyRA69+CDFiSZQ?1*|hS^36fyS zoXPD1?A(x-)J!X%;0@!%sil3QIOqG(Z;iDVp8nf)@;`es>c@|A=l${%sKfR}ED%}^ z=nBcUygxy@*~@;-^XTwAf+0%|TO%!H z!fZKTyw`z2?5pl#US64aaqZIQ{*45c{W=Bps?B$)=FJrZ9T}(lh%RtgOHNp_h z82s4+=zaBOZ_2BUIAyj;ZihIA@U2RpQie(2vn)t4*GH9X#I>1NZm<76V)hICIwz_p z7}ik5IF%MM{3eOoOdFisaG+Jrxz;<7ap+K3B;a*Ud6B~H;2&SE&!qHt25U7_I(i(< zeAXzCSEfN#jJ(P$lW2dnY2tYif{femR6XAAQitwPJ;O7Tf;yJN-Z#|fWSU3&_FaAX zDk)2Atwj5bnT8?Xy(R7x5+Pw()&%{xIL<1Km?HG?fiZ$tRiD@-3`}lb^S%A|DhDZk zqw;3t7mF4?VILeBVoUzAp;6i#S2U{ONO{*fFC~0%(r9ekMvvalQXW&~=SRR)r_jcBC z^Ccbqz$Co30TWa15odD3_P43ZUEqStz4{;+(0Phou>Hz1aocuq^eOZEk%PQ=TSkCK zA(G3_KcE{W>i~S8Vg}=m--*}4O<_*1=+31(kpKQfbh(-&-5bT}K9jG5-rmiiV(tKZ z#HCg5ypA0H9uH-c^`ut|mmAtTSVHrx8G4Z-Q9XSQ zlIP;q256^Mz|o~D@!-`F&&S~3CA~V{zUGMxD=+vSMQiUa^=I5~pd2X^2g4?DG#vqj zhWXai!a7uYWQR~u=xemPdQg=#XzlE{w`d%>?}K>Lr%M*nHsbQiMoU;mM@q>?0j|;z zYfrM=FtFCytqi;G4<9;wA?{5!iE1(g2TH3NmcO0n{sm_6rQi(j7Q?63t_FuNl1jdF z1xMxT+T43BIYBQxOb~V~gFtD8Wa9vPS`|t@G8xc@I`@8a5HQKhGcH%II|-#Pae&Qb z+ve7Jjg>^4|s zXV~JFi7H~ zdAL|tkJO(`DFRm|^)Y}4Jci#d_vf3U#KeY0Uir*gh4rUi_$*S21r0m@f9)%Phf>~F z;@|DrK<~(z9z{B}{(Bf?&<4?vaESf^tN$8%4vYkunW zcNzIN(^F31!FmB7vb){>UB)H{oq>fC@r!@mocKkBywbkwNc?ZP9KnXl<$~J(!`@d$ zMYZ;QDN>#XxZU}o=YU-AF{YPSab7UKOHl^sR@z)~J@1Lc3O%OcFW-0OFm{`Y10 zfOWYVK|J(d)+HynibP3Q8qt4WhB5fS|Hro_V@s-2YM(l-2w2y}^b3AGrvmuzFJE@J z5jTwd&!z~|7tKR7Hd@BnXZ`=Fp!%~+mt&Nvr^aVRG5%-w3!ZAI7_jd0xwng&{QI$L zfDc5|@NU5subB_v38JAYkSxBj|8WWgD_=0%@0M7P zZhc_{cg2k=+5#c4lMaI?)~M7%^w<3`h~pvPbe}$biUln6t-E)lx@DaofMbqygQ9Y8 z1ISwqxA`6_@F<8IK%pL{dCXIKEWJ;7S5Z-6_4xAT%lzgogtM|#(!Sn3&c2xO=e!uqqu-P3 z*Av9La}_~0khSYwBX`jlU0E&fJFL4!}q8!{q;AMbJB*sV=y>v-`}gw z0h!Tr0`9W9TD)2k|GKPn-<$FA@hw-LCA(1J?I=%o3iJpDx&vtaVzS&$gFv{Ogn3t2 z*Nj`G{N!aOIh+n*yHS>ji3#9eG*u9ZZlzKB+p_3m zadCfnc=|09%v2)AS6U1HYuYlYnv4`$I5!fBBqle%d&hzik3Yf)$t~KiT0eR6Lda&Y zjsE;V*@gM=U^b>Bk@UJZC` zFuvpJbszP$vRPuh>z=)CR+sZ6_rk8QA$Rn9y1P-yD?1YKW}q|!vSSvPs%~0gLBY2B z>XH`Zsjh>%5|GKVGEp%gnVjq3sf`1&-LZ0Z>6djG(2Qx?tC8b)zlJsXg8hf}ie+wQ zHy0O|xmWu3eCQ50Y=~70fk;e(p<5DZ_5Qd^3~5tyb2I7>JtNZJ-=&+IiHld6g081V zVf~F_1dbg`B5nk(acpscd29QptTgdyZmX`>fU{LGxd9Q7@RItK1(46#tD!L`_1B^{ zf#B73JYHBf^4@H&ZptkhYid#uNGI-qK0E*#NC|D+lgRw0?(S~?WjVu8p9Ak2jpX%j z_8F%nPbCO1jG2uKk_}U0sTD+=G8jQADuQrk?Y5)EFbltB53??VgJGZ#NftqUsa5i^7GRm=B8UdfcIX5@w+{PHv zW}TI4xpz^s+H?0w{fP|1hgT|?qouWVf3$gPuH$mp6B89w&|yf7 z>JxZBe$ zr4CHQfWW}3@S7Rk&f6HNCvKOecsplLTSRJ1rR4KdeD+4>19|g3c$LGB~$#@F#^29)tQ40DDfW96)Ogx1?$8Ov1?>~N2 zA(mL-9RVse&!f$A|MF&0ZWfY8Cq^tB5;|yj6g$KD(NJyY^#o(za+7cf7aZ%%Ok|X9 z8f?<;QX{YbT3N{W3ktOMdnRSNdptxl(xRO)m0Rr`Q8JBPA8Tb3efKM;;X<&3W9ZbyqKjhg!s5P^4wHCF*7R>55e#wPqG0DN&VvVsc2nO_uDmSsLF8 zxblRlv%S)J}v|Ucfj|`o(jS5tUc*^QOxs8;Y6v-IwZ8d_1<8=RXDU zt(>gM(Uz6}#Rp2Y3a5R?va zz^Wiiaf7evf!EebQP1lTXB5$10PZ7bXDgjoHqFy-vTj;E5@%-&_fZ13D3~4rbqZBx zKZ;~WnIOi$b{I@`^s4ou-u(??TK%=8zs3_M^#c6$;PeQjnViRnCZE!N?fD#~8AZb( z5Thi3@t8trI|UIG;P)N_A#*!IItISg=6@WpfL$U|K*vOb1*6kOM53PsWIR)LKRY?R zSQ?^(6oE}z#oey&T3^nqwTH`Y5TBmy*Ctf1*HjLnpqUa#Ni z-2rm_*S=27g!Su!QmQLyc)7 zL6kYA(33dQLmkWN2M2e?b|E`#Zd{~U(7Hx8mf~>o#!J^v9zxe5K0!CUXJm=xkemKQ zvD83HN@7!XU})+GeKjmX_~z=|cazW-Nb3d^UlRr`L0mwFcpw;%xCRKFXSntRj19p~jg)QAS*dtz zpf8|x#$Y8xPge(Y9;F62Q%~JA{CGUr81$>ddIh3z>Sa$b=cVk&X$THzRch&Wr4U=bubZys%s@E z`p`i1X9vQ1qWW3^9JTuH*6tN|?J?m81vcd}5Sb11YpR>_3RuA=#>lBG0abyYi*i?I z>aPXg{2-{)cqx0SFRR06e}dv+s_}A8vu_UA0&jgJ`MDyzM~el{haRCo5X|+IAo3cW z0kDQ#61UpoSa4$O9Hfjw!*dP;P_H?*U zAYY#)1~VoX-Cr5?0o?OzvEm^Em#F^1!FGWjxOvlCLx(A}om04BC5@2=!kF}kP~CyI z5|~*$!O1~XCFB;laimvI&_mdCV+ANSmPjkR?HCQ5t`L&&*?aQEz>pitoeM#!F5P0_ zsL}m+j-k<8Dx;(rQ6cwIhNT)kLNuPWc6M_RTj>`!mp24KM!QKN+AD8F3{>TPBpKlp znO}Vl`R&_tH@&L&`I>X@&@^QzEnR*V_RMXDvQEy~>u? zMn!@CRvPF}w;4##bH(5Au*oV9&My;Ay4mzHd9ou`DbW&toy040*bJ~7gc5(E3629E zJPER?_##W@)ytPx9vA2$3vWTI&Rh;ts59;|baZqq>#kJAJ{!7*`b&NEVEC2n z)<(|dXR95{F;kt6uYIQHq;V7s>y9JXsk+;67o571MSA>-($55IdH8)E>TG%C?v_k@$gth% zK3)$8VN$x^$t-to4^$3!e+ZUFg;FVqH80eTcPp?yV4ak3?7& zL@=koJ)CvuycZhZ)zBFc5m#r9M4alQPb37$qHP{OCi2en2{V*vZX8UG#!M`U4VP}N zUkh>uJ0rTpEAXy_24xMF4tt?9iE9$(1v zXVcnW!*Bs>qopDFqPo(@K#FY*Mn_F!vTavWwb+Q{gBp8CP95Q^{3jPe3hpOoSdlNou>uOu|pTtK(|T`*OIY02d{`n3pVxc49hYY0O*M6EMxG3+ zAgy9-fEg>6z7DeJlwmeRS8=Vno5fm(;&H88awZ~cnil`_INM(fjF2$^({!=19J<#Y zr#{fu#sy||)pel5DI!iYwR&h4d`t^gt>pyp5KP_w-0G*?hDE4;gA7jV)I@srC1g&5 zUqIhM;K_zu6%K!#-eHIF*>{@(-{Vz~!Yiz^M2;vfm;h#--j3sEv;oYVdh%~_z+4i_ zvd%iETr#8Ze?EuUTNyzP6k2q=`}D~g*FKTNM)hPRzcBrBHzZPJu(J6oVLw@j=}LEb zyIY$mH;n+7AQm(?G2Mn<99gKv+Z*B8AIpzFsS;}`{cFytVDnN?>{^+AR$JM?iX^(!eFL7T#Ykm0vY``_9SUm};+qUlhj?OSL(@Z!SLe~=2->| z3;julhv1>Cvxks}O?9C|=_0B${uz zMsndfg1~H87rFRdl_wr}QOkxjoH!v)-t$RTKjO8GVzrHZ20Be!x}=`CH#i2l0n6kX zuk=CPC6YMcApKrkzS%;38Lxm>go?(RIYcHq+%V;0e@9E>=QG>+&Lrph%M^*0iH&|M z1+A~4zIOs-eqg_$+;GR9ftEaByOYR}EaI@{P%4q#>O0*fUnbas{Dc(>s}K5Il7kE3h`RxDGp;my4YpucK(WR)oWI^ZDRfUu~C$ zPgaQg$2RA`x3?y2V$tafJ|xr;t#TkT>b5Ej{JkE4nlv}~J&d~t6c@Pbz%q<(} zUNJ46Oom+kTDB>DEO{lYNcCLhoXySJD#m~8Zy1h{<08Vcy}dp1apPT%V8b$;dWZvV z@RZCxseO7-FP%s)swMYYLFO6-t17f1EIYT``L2&0NhW~<1-$CUwP(C8dgs{F=%Bl) z;w9%Tjr~}Yo`_62q4B!0{2qagxl)XXx$=9qOY*BJ~W#Q$@)h6rRYD%^j{bi5~8uv((<*T_);(* z4?uxfO66H_pE#LQ@)t7O2-=p-ziPp09;Ro#?68t9PxlM?`GYtJ27jsq*-mnLqpr3z-G;C7#Gxa?ry)Jd1d*nh&G3ZxgqCvJ#SG7?|DwNkU4 zy55E)qK=$yrmnR!#lC219sS>;epsMR;IB!QyNUme zZZgRN7?~_?1oy8~3v>5>{wLFf2Lj$`#3We$4SZq)uuMh6lK!uA;UCf&QZ|57I{jFA z{*PPw^Pw=10SNIw-F?5lOCbdeL{n^${J(Ke6{w@@7sWTs{|+z$-yO>$L-Jo~J%)f# z3;_ju;`U!yrdwbz{?lsmYt}GfxGuAgBmafaV!>lkC@j)AHlxeYxK;2EWSx-fVnT;d zF|FetRS9n}_8X;*JvQfKed*xHGMnEwG|URYCM)gs%9mMo$K!>o0IoHWm3Jj@=uo-; z9-(WJU61?kUuM7xf#71oS^ZDC=z7u0lJw2BlSrqLb+^c~sa5Bi|5#;_{z469@fcGSrdPNxpW~5M7^I@%-Z=WQxf`5h_L7F{~Ln znKo0U_7hKZz&0kNm*iyddw%7sLi1z&0dPqMq8Z?od$bp_-G^${$utnp!7uSCnhd{u zIFc2Na8@C4xML_m!})6(*Ky0$O}>>~(ObBzxW?_g<&nq&oyC50dqaF7q)AkpIQ{pv zDG^@uiouBmb-kGy2T3rt8y*&EYNO4$j9wl$WVUE`Z8R&S65E50<=1Clz-xkBT&UK| zKQ4!fUBv`4nNTo8HS3pq2JwxL)#{hkLw{Axmi_aLM%9rDlAg{RPIAHAn3xY2=t_3*|brKk!xGclv=tO@ah zje~#twG>oLK0=1Q1dr{H>)p>S+-U!}4+biTg24uk#N)F9LM3F!@7)C4+J=$cLC;0Z zZ9%01Os`d)sb4+}5j?rl#`Vw7#1mcbJz1YZRyeHvG4O84x_tb}R7%m(|A=P#bH@p& z0m%2BV#xCEPxy6tX0I?LN}4!v%0Djo&mU&+U}-WUQn%%fs5t+FmLjCTI9WtQ@3nUa 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 ao4bF!pCXN(e*5)b;P0ZEjw((C9q>QZ>~^mJ literal 75964 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�P#GEgf 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 F{{a!jH8}tP diff --git a/docs/tutorials/gcp_before_you_begin.md b/docs/tutorials/gcp_before_you_begin.md index 7dbe05c5..13296dfe 100644 --- a/docs/tutorials/gcp_before_you_begin.md +++ b/docs/tutorials/gcp_before_you_begin.md @@ -31,3 +31,4 @@ This tutorial depends on resources provided by the Google Cloud Platform. and click through to Enable it. * For more information about enabling the Compute Engine API, see [Getting Started](https://cloud.google.com/apis/docs/getting-started) in + the Google Cloud documentation. diff --git a/docs/tutorials/gcp_cpu.md b/docs/tutorials/gcp_cpu.md index 59b52ef0..03571ccc 100644 --- a/docs/tutorials/gcp_cpu.md +++ b/docs/tutorials/gcp_cpu.md @@ -40,21 +40,22 @@ for your project. Use SSH to create an encrypted tunnel from your computer to your VM and redirect a local port to your VM over the tunnel. -1. Follow the instructions in the +1. Install the `gcloud` command line tool. Follow the instructions in the [Installing Cloud SDK](https://cloud.google.com/sdk/docs/install) - documentation to install the `gcloud` command line tool. -1. After installation, initialize the Google Cloud environment by using the - `gcloud init` command. You need to provide details about your VM, such as - the project name and the region where your VM is located. + documentation. +2. After installation, run the `gcloud init` command to initialize the Google + Cloud environment. You need to provide the `gcloud` tool with details + about your VM, such as the project name and the region where your VM is + located. 1. You can verify your environment by using the `gcloud config list` command. -1. Create an SSH tunnel and redirect a local port to use the tunnel by typing +3. Create an SSH tunnel and redirect a local port to use the tunnel by typing the following command in a terminal window on your computer. Replace `[YOUR_INSTANCE_NAME]` with the name of your VM. -``` -gcloud compute ssh [YOUR_INSTANCE_NAME] -- -L 8888:localhost:8888 -``` + ``` + gcloud compute ssh [YOUR_INSTANCE_NAME] -- -L 8888:localhost:8888 + ``` When the command completes successfully, your prompt changes from your local machine to your virtual machine. @@ -62,7 +63,7 @@ machine to your virtual machine. ## 3. Start the qsim Docker container on your virtual machine On your VM, start the qsim container by typing the following command at the -command prompt on your VM from the previous step. +command prompt, on your VM from the previous step. ``` docker run -v `pwd`:/homedir -p 8888:8888 gcr.io/quantum-builds/github.com/quantumlib/jupyter_qsim:latest & @@ -100,7 +101,7 @@ and run your code. * {Colab} You can write code in a Colab notebook, and use your VM to run your code. In - this step, we use the + this scenario, we use the [Get Started with qsimcirq Colab notebook](https://quantumai.google/qsim/tutorials/qsimcirq). 1. Open the @@ -143,7 +144,7 @@ and run your code. **Before you begin** For this scenario, you can connect to your machine directly over SSH rather than - create a tunnel. In step 3.3 above, remove the second half of the command. + create a tunnel. In step 2.3 above, remove the second half of the command. Instead of the following command: ``` @@ -161,8 +162,8 @@ and run your code. **1. Copy the container ID for your qsim Docker container** - Use the `docker ps` command to display the container ID. The output should look - like the following text: + Run `docker ps` to display the container ID. The output should look like + the following text: ``` CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES i @@ -173,8 +174,8 @@ and run your code. **2. Connect to your qsim Docker container** - Use the docker exec command line tool to login to your container. Replace - `[CONTAINER_ID]` with the ID that you copied in step 1. + Run `docker exec` to login to your container. Replace `[CONTAINER_ID]` + with the ID that you copied in step 1. ``` docker exec -it [CONTAINER_ID] /bin/bash @@ -187,6 +188,7 @@ and run your code. You can use the code below to verify that qsim uses your qsim installation. You can paste the code directly into the REPL, or paste the code in a file. + ``` # Import Cirq and qsim import cirq @@ -216,25 +218,10 @@ and run your code. [(0.7071067690849304+0j), 0j] ``` -## Clean up - -After you finish either tutorial, you can avoid continued billing by stopping or -deleting the VM instance that you create; visit the -[Compute Instances dashboard](https://pantheon.corp.google.com/compute/instances) -to manage your VM. - -For more information about managing your VM, see the following documentation -from Google Cloud: - -* [Stopping and starting a VM](https://cloud.google.com/compute/docs/instances/stop-start-instance) -* [Suspending and resuming an instance](https://cloud.google.com/compute/docs/instances/suspend-resume-instance) -* [Deleting a VM instance](https://cloud.google.com/compute/docs/instances/deleting-instance) - ## Next steps After you finish, don't forget to stop or delete your VM on the Compute -Instances dashboard. For more information, see the -[Clean Up section in the Overview](https://quantumai.google/qsim/tutorials/gcp_overview#clean_up). +Instances dashboard. You are now ready to run your own large simulations on Google Cloud. If you want to try a large circuit on Google Cloud, you can connect the @@ -242,6 +229,13 @@ to try a large circuit on Google Cloud, you can connect the Colab notebook to your VM ([documentation](https://quantumai.google/qsim/tutorials/q32d14)). +For more information about managing your VM, see the following documentation +from Google Cloud: + +* [Stopping and starting a VM](https://cloud.google.com/compute/docs/instances/stop-start-instance) +* [Suspending and resuming an instance](https://cloud.google.com/compute/docs/instances/suspend-resume-instance) +* [Deleting a VM instance](https://cloud.google.com/compute/docs/instances/deleting-instance) + As an alternative to Google Cloud, you can download the Docker container or the qsim source code to run quantum simulations on your own high-performance computing platform. diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index 6ce85650..8307aaac 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -18,7 +18,7 @@ instance heading, ensure that your VM has the following properties: * In the Machine Configuration section, in the Machine Family options, click on the **GPU** filter. * In the Machine Configuration section, in the Machine family options, in the - GPU type option, choose **NVIDIA Tesla A100** + GPU type option, choose **NVIDIA Tesla A100**. * In the Number of GPUs option, choose **1**. * In the Boot disk section, click the **Change** button. * In the Operating System option, choose **Ubuntu**. @@ -34,49 +34,56 @@ for your project. ### Find out more +* [Choosing hardware for your qsim simulation]() * [Choosing the right machine family and type](https://cloud.google.com/blog/products/compute/choose-the-right-google-compute-engine-machine-type-for-you) * [Creating a VM with attached GPUs](https://cloud.google.com/compute/docs/gpus/create-vm-with-gpus#create-new-gpu-vm) ## 2. Prepare your computer -Use SSH to create an encrypted tunnel from your computer to your VM and redirect -a local port to your VM over the tunnel. +Use SSH in the `glcoud` tool to communicate with your VM. -1. Follow the instructions in the +1. Install the `gcloud` command line tool. Follow the instructions in the [Installing Cloud SDK](https://cloud.google.com/sdk/docs/install) - documentation to install the `gcloud` command line tool. -2. After installation, initialize the Google Cloud environment by using the - `gcloud init` command. You need to provide details about your VM, such as - the project name and the region where your VM is located. + documentation. +2. After installation, run the `gcloud init` command to initialize the Google + Cloud environment. You need to provide the `gcloud` tool with details + about your VM, such as the project name and the region where your VM is + located. 1. You can verify your environment by using the `gcloud config list` command. -3. Create an SSH tunnel and redirect a local port to use the tunnel by typing - the following command in a terminal window on your computer. Replace - `[YOUR_INSTANCE_NAME]` with the name of your VM. +3. Connect to your VM by using SSH. Replace `[YOUR_INSTANCE_NAME]` with the + name of your VM. -```shell -gcloud compute ssh [YOUR_INSTANCE_NAME] -``` + ```shell + gcloud compute ssh [YOUR_INSTANCE_NAME] + ``` When the command completes successfully, your prompt changes from your local machine to your virtual machine. -## 3. Enable your virutal machine to use the GPu +## 3. Enable your virutal machine to use the GPU 1. Install the GPU driver. In the Google Cloud documentation, in the Installing - GPU drivers guide, follow the steps provided: - * In - [the Examples section](https://cloud.google.com/compute/docs/gpus/install-drivers-gpu#examples), + GPU drivers guide, follow the steps provided in the following sections: + * [Examples](https://cloud.google.com/compute/docs/gpus/install-drivers-gpu#examples), under the Ubuntu tab. Only follow the steps for Ubuntu 20.04 (steps 3a through 3f). - * In the - [Verifying the GPU driver install](https://cloud.google.com/compute/docs/gpus/install-drivers-gpu#verify-driver-install) - section. -2. Run `sudo apt install -y nvidia-cuda-toolkit` to install the CUDA toolkit. + * [Verifying the GPU driver install](https://cloud.google.com/compute/docs/gpus/install-drivers-gpu#verify-driver-install) +2. Install the CUDA toolkit. + + ```shell + sudo apt install -y nvidia-cuda-toolkit` + ``` + 3. Add your CUDA toolkit to the environment search path. - 1. Run `ls /usr/local` to discover the directory of the CUDA toolkit that - you installed. The toolkit will be the highest number that looks like - the pattern `cuda-XX.Y`. The output of the command should resemble the + 1. Discover the directory of the CUDA toolkit that you installed. + + ```shell + ls /usr/local + ``` + + The toolkit is the highest number that looks like the pattern + `cuda-XX.Y`. The output of the command should resemble the following: ```shell @@ -84,9 +91,9 @@ machine to your virtual machine. ``` In this case, the directory is `cuda-11.4`. - 2. Add the CUDA toolkit path to your environment by appending the following - line to your `~/.bashrc` file. Replace `[DIR]` with the CUDA directory that - you discovered in the previous step. + 2. Add the CUDA toolkit path to your environment. You can run the following + command to append the path to your `~/.bashrc` file. Replace `[DIR]` + with the CUDA directory that you discovered in the previous step. ```shell echo "export PATH=/usr/local/[DIR]/bin${PATH:+:${PATH}}" >> ~/.bashrc @@ -96,29 +103,34 @@ machine to your virtual machine. ## 4. Install build tools -Run the following command to install the Cmake tool and the pip tool. This step -might take a few minutes to complete. +Install the tools required to build qsim. This step might take a few minutes to +complete. ```shell -sudo apt install cmake && sudo apt install pip +sudo apt install cmake && sudo apt install pip && pip install pybind11 ``` ## 5. Create a GPU-enabled version of qsim -1. Run the following command to clone the qsim repository. +1. Clone the qsim repository. ```shell git clone https://github.com/quantumlib/qsim.git ``` -2. Run `cd qsim`to change your working directory to qsim. -3. Run `make`to compile qsim. When CUDA toolkit is installed during a qsim - compilation, make builds the GPU version automatically. +2. Run `cd qsim` to change your working directory to qsim. +3. Run `make` to compile qsim. When make detects the CUDA toolkit during + compilation, make builds the GPU version of qsim automatically. 4. Run `pip install .` to install your local version of qsimcirq. -5. Run `python3 -c "import qsimcirq; print(qsimcirq.qsim_gpu)"`to verify your - installation. If the installation completed successfully, the output from - the command should resemble the following: +5. Verify your installation. + + ```shell + python3 -c "import qsimcirq; print(qsimcirq.qsim_gpu)" + ``` + + If the installation completed successfully, the output from the command + should resemble the following: ```shell @@ -128,17 +140,7 @@ sudo apt install cmake && sudo apt install pip ## 6. Verify your installation You can use the code below to verify that qsim uses your GPU. You can paste the -code directly into the REPL, or paste the code in a file and ask the Python -interpreter to run it. If you paste the code into a file, use the following -command in the interpreter to run it. Replace `[FILE]` with the name of the file -that you create. - -``` ->>> exec(open("[FILE]").read()) -``` - -In the `qsim` directory, run `python3` to start the Python REPL. Run the -following code. +code directly into the REPL, or paste the code in a file. ``` # Import Cirq and qsim @@ -170,26 +172,11 @@ After a moment, you should see a result that looks similar to the following. [(0.7071067690849304+0j), 0j] ``` -## Clean up - -After you finish either tutorial, you can avoid continued billing by stopping or -deleting the VM instance that you create; visit the -[Compute Instances dashboard](https://pantheon.corp.google.com/compute/instances) -to manage your VM. - -For more information about managing your VM, see the following documentation -from Google Cloud: - -* [Stopping and starting a VM](https://cloud.google.com/compute/docs/instances/stop-start-instance) -* [Suspending and resuming an instance](https://cloud.google.com/compute/docs/instances/suspend-resume-instance) -* [Deleting a VM instance](https://cloud.google.com/compute/docs/instances/deleting-instance) - ## Next steps After you finish, don't forget to stop or delete your VM on the Compute -Instances dashboard. For more information, see the -[Clean Up section in the Overview](https://quantumai.google/qsim/tutorials/gcp_overview#clean_up). +Instances dashboard. You are now ready to run your own large simulations on Google Cloud. For sample code of a large circuit, see the [Simulate a large -circuit](https://quantumai.google/qsim/tutorials/q32d14) example. +circuit](https://quantumai.google/qsim/tutorials/q32d14) tutorial. From 4848a4c01f4ad21e4d3e6fa4e19dfe8d2514a0b5 Mon Sep 17 00:00:00 2001 From: Ricardo Olenewa Date: Tue, 5 Oct 2021 19:36:36 +0000 Subject: [PATCH 111/246] Remove refernce to choosing hardware doc (not published yet). --- docs/tutorials/gcp_cpu.md | 5 +++-- docs/tutorials/gcp_gpu.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/gcp_cpu.md b/docs/tutorials/gcp_cpu.md index 03571ccc..a986ddbb 100644 --- a/docs/tutorials/gcp_cpu.md +++ b/docs/tutorials/gcp_cpu.md @@ -18,8 +18,9 @@ instance heading, ensure that your VM has the following properties: * In the Machine Configuration section, in the Machine family options, in the Machine type option, choose **c2-standard-16**. This option gives you 16 virtual CPUS and 64MB of RAM. - * This choice is for demonstration purposes only. For a live - experiment, see [Choosing hardware for your qsim simulation](). + * This choice is for demonstration purposes only. + * In the Boot disk section, click the Change button, and choose Container-Optimized OS. This overrides step 3 in the quickstart guide. * In the Firewall section, ensure that both the **Allow HTTP traffic** diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index 8307aaac..cae908bf 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -34,7 +34,7 @@ for your project. ### Find out more -* [Choosing hardware for your qsim simulation]() + * [Choosing the right machine family and type](https://cloud.google.com/blog/products/compute/choose-the-right-google-compute-engine-machine-type-for-you) * [Creating a VM with attached GPUs](https://cloud.google.com/compute/docs/gpus/create-vm-with-gpus#create-new-gpu-vm) From 8b6b275b3b02eadacdd56d4d3c45d0b9f9d87bdb Mon Sep 17 00:00:00 2001 From: Ricardo Olenewa Date: Tue, 5 Oct 2021 22:04:59 +0000 Subject: [PATCH 112/246] Added a few words to connect machine runtime to billing. --- docs/tutorials/gcp_cpu.md | 2 +- docs/tutorials/gcp_gpu.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/gcp_cpu.md b/docs/tutorials/gcp_cpu.md index a986ddbb..d75362b4 100644 --- a/docs/tutorials/gcp_cpu.md +++ b/docs/tutorials/gcp_cpu.md @@ -222,7 +222,7 @@ and run your code. ## Next steps After you finish, don't forget to stop or delete your VM on the Compute -Instances dashboard. +Instances dashboard to prevent further billing. You are now ready to run your own large simulations on Google Cloud. If you want to try a large circuit on Google Cloud, you can connect the diff --git a/docs/tutorials/gcp_gpu.md b/docs/tutorials/gcp_gpu.md index cae908bf..34725ce8 100644 --- a/docs/tutorials/gcp_gpu.md +++ b/docs/tutorials/gcp_gpu.md @@ -175,7 +175,7 @@ After a moment, you should see a result that looks similar to the following. ## Next steps After you finish, don't forget to stop or delete your VM on the Compute -Instances dashboard. +Instances dashboard to prevent further billing. You are now ready to run your own large simulations on Google Cloud. For sample code of a large circuit, see the [Simulate a large From f45b6c64d4b1b134d315e561358b67ed093ad517 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 6 Oct 2021 08:11:18 -0700 Subject: [PATCH 113/246] retrigger checks From 9ee08d4a909d41580fc9f48d7c6618d1c4453c87 Mon Sep 17 00:00:00 2001 From: jlow2397 <54644848+jlow2397@users.noreply.github.com> Date: Wed, 6 Oct 2021 09:07:18 -0700 Subject: [PATCH 114/246] Removing qsimh content from marketing page --- docs/_index.yaml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/docs/_index.yaml b/docs/_index.yaml index 0b93d76c..1d657862 100644 --- a/docs/_index.yaml +++ b/docs/_index.yaml @@ -15,7 +15,7 @@ book_path: /qsim/_book.yaml project_path: /qsim/_project.yaml description: > - Quantum circuit simulators qsim and qsimh. + Quantum circuit simulators qsim. landing_page: custom_css_path: /site-assets/css/style.css rows: @@ -24,7 +24,7 @@ landing_page: - hero - description-50 - padding-large - heading: qsim and qsimh + heading: qsim icon: path: /site-assets/images/icons/icon_qsim.png description: > @@ -39,12 +39,6 @@ landing_page: qsim is integrated with Cirq and can be used to run simulations of up to 40 qubits on a 90 core Intel Xeon workstation.