From 5c1ed1b4cc96585e6d9c470581a84194b58f43d7 Mon Sep 17 00:00:00 2001 From: vinayak19th Date: Mon, 22 Sep 2025 00:45:42 -0700 Subject: [PATCH 01/14] Added demo --- demonstrations_v2/keras_3_training/demo.py | 15 ++++++++++ .../keras_3_training/metadata.json | 26 ++++++++++++++++++ .../keras_3_training/model.keras | Bin 0 -> 17039 bytes .../keras_3_training/requirements.in | 2 ++ .../keras_3_training/requirements_v1.in | 1 + 5 files changed, 44 insertions(+) create mode 100644 demonstrations_v2/keras_3_training/demo.py create mode 100644 demonstrations_v2/keras_3_training/metadata.json create mode 100644 demonstrations_v2/keras_3_training/model.keras create mode 100644 demonstrations_v2/keras_3_training/requirements.in create mode 100644 demonstrations_v2/keras_3_training/requirements_v1.in diff --git a/demonstrations_v2/keras_3_training/demo.py b/demonstrations_v2/keras_3_training/demo.py new file mode 100644 index 0000000000..e82e7c703d --- /dev/null +++ b/demonstrations_v2/keras_3_training/demo.py @@ -0,0 +1,15 @@ +r""" +Keras 3 Training += + +Introduce your demo here! +""" + +print("Hello") + +############################################################################### +# +# Add comment blocks to separate code blocks +# + +print("World") \ No newline at end of file diff --git a/demonstrations_v2/keras_3_training/metadata.json b/demonstrations_v2/keras_3_training/metadata.json new file mode 100644 index 0000000000..8b3b93a9f7 --- /dev/null +++ b/demonstrations_v2/keras_3_training/metadata.json @@ -0,0 +1,26 @@ +{ + "title": "Keras 3 Training", + "authors": [ + { + "username": "vinayak19th" + } + ], + "executable_stable": true, + "executable_latest": true, + "dateOfPublication": "2025-09-22T00:00:00+00:00", + "dateOfLastModification": "2025-09-22T00:00:00+00:00", + "categories": [], + "tags": [], + "previewImages": [ + { + "type": "thumbnail", + "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_placeholder.png" + } + ], + "seoDescription": "A demo showing how to integrate a pennylane circuit into keras 3 and train it using both pytorch and tensorflow", + "doi": "", + "references": [], + "basedOnPapers": [], + "referencedByPapers": [], + "relatedContent": [] +} \ No newline at end of file diff --git a/demonstrations_v2/keras_3_training/model.keras b/demonstrations_v2/keras_3_training/model.keras new file mode 100644 index 0000000000000000000000000000000000000000..e9f3f91dbfb9a0112f27ac94618cdafcf46d110f GIT binary patch literal 17039 zcmeHPZ)_Y#6`%7TVkK$n(ugJ@jjl%!PMaL(e`%be-i0Q?khHa%K%<6eJ-4?$Z@jmA z-aoO8n-bI(xgtgRkV=uFLM4P0;6p;1*Spr;?0fTO-kaZh`(}1_eM5&@+cq+JJiy-j#e0)EAns=jfx>u>#Q7{kvxP-S}Vs9jWwN?svyEbK|BOb5Q12A<^v>CwnFLr~8h zRBJjs2n?5(gz&-II?*F^GLs^K;fT9oRC*k{!semRZI|bTs?d)tU^#pm#s{sA&!luS zR9&6kue;?aJd6#6O^bT-JT%9QV2qd1zTI6ITwC!m8uI4yXoQOD!q?mlc7l(`?E z;3#(jw8iJeq5zZ_G!7dT8p52^X%w`Ib`V;=RUj;+s>;Bt8-Tp+=Do-TVE{sm^#t@% zHD1-a7Tw`P=>DaKNoFZ{DOgPFO zAOe#+wp#?MJDmaAw0+aab93BsMdUhmaV&H}B0Ul1?NBI2k)qEF23!tfo)F`Olb-F0 zYBwqwleRB#>|WZEl!We44Z(b5I|V}qJ{U9uZb7%!g$s5{L)Ia#^W;p5`$ZMMwf-{< zdHsf#8+g89XrR`q1+C<*>L`_+j6uA4{xq0+j$VPBQ{0KLPt`gKEW;{sU{D$X$|!jy3v(f`GwFc=(2ZOVlKZ4@LoOfnt%=BT z;o7ToktCjw#xJlvGCdDMRuwn;6|vA?Ooik0hl5 z<-m5t#L{i;$~+7rycA&1AR|m)`syMVqIn=$?J9JBXuC_eh^CPmH>FAz2dgI{j+xZ* zeHbEGu|$kjnxjbq7wNgG(L%EDZL^9YC7|QF8b5Lkix(~5LA2FGB)~JumTy>7mKhFsfgDKGF7wd#rECD&GRq(_MHL7@Y9JCInH=#Wn+4*|E0nG2k%MSZhuE zcB=U8g4HS>BjTGe8NZ9yhaSI5EG||l0($&X9-CEpjbNep6v0l+|D!ze#FP6mhB5c! zF?*jZNzY|Pzq48LDNmo-Su1x*(bv+FPxdEUYvl$NJ*JIIO0C>)nz!`vtrxe*ICHhL zjjV&=@(xG2Igt>UBkmGwi5TI&9{95=ham53k3D%<)G0?*%Rx0$%U*HAiZrD?yko|r zYCSDzN zL5TLBGSr|+*(iR?TV%s?TV>fSemftKaIP6w%C znWfw(Z-E_Lc?_z!rT+Ceru;gWlJX?CplJ5RWg4==n>Bh~c1+3Nxs*K(hi;d3r}UWU)U z8m%|<{M&53v8PMAgW_vdI42%t1pz(J(0U`O&EL0rz0qi%JhMZZHli$S8ov)o!8m>u zV5_WX6u%2!ss)1X<7gfmtgZgYls->Rsq2OMe@)R>H9J1CqEC4PnkRLYzgL3q zfZ%&6!Y9fx2_`4JM2lq5dm@d?O``oh$*fuv`-rdwkAG! zP`XZ!w5i+b(Qj^j;{ECC_vU`}gE#(o^RpXr=G#ZMr+@hG?3L%9&F^^d_u0R0INyHe zUw@eY_ESgp|NPPQxo2PIXYYHiEw^^~PuG4u-kE!R3qPE>@sF$?{|hQ_-`2scK;@0r zar*ma&E`SYBL^SPgDWryZh^^-=E1+K_Z3OSC$)|n?3Efvl;URbd$msrCY5|#tS$uf z_?5FXW0$&XaiDb^u~ph*D%|8;4^2p}wsk$F&tthSOBuCXU}K7&C581}#2i3?)^R#s z%U#Fed%3jVf#>z>I62dipYRQDJnsaytjPhzVD;B%eKw~uj+7R{;_J1=(>wU=8D*P{ z-!?U7xL)W&Xka_t-*@b8AfVW^zn^^g!{w5ydTP&y%iZa<&Espi_W_#}f_j`?xT7JN z+xFu1;kh4W>v8E4c7Oc$PZzjghMHJ>wb*{4Up`~ClJdw%}n-REwU){SS$+S`Zg z?0~f4&cb!Ox#sFCFTA_w#<>HJJ^y-;4SR3?IqEr>J$wF Date: Mon, 22 Sep 2025 00:50:55 -0700 Subject: [PATCH 02/14] Converted notebook to demo --- ...0aec1f9e-ee6b-4237-8372-32396d5dd85a_1.png | Bin 0 -> 19068 bytes ...20c945e3-08f4-4e92-a98e-4dd6392ae14e_1.png | Bin 0 -> 7726 bytes ...234a1746-683d-4d8b-8674-4681a3b2cff5_1.png | Bin 0 -> 29968 bytes ...23cde79d-79d0-40da-b8f7-a80cfd8d8fc1_1.png | Bin 0 -> 21158 bytes ...78759557-918c-4b68-b975-65e4e9f2d0f4_1.png | Bin 0 -> 7726 bytes ...d716da26-80d9-41f4-ab6a-567bba016fd6_1.png | Bin 0 -> 29968 bytes ...d9dbeff1-fdad-423e-80ef-f7e8439a20f0_1.png | Bin 0 -> 25196 bytes demonstrations_v2/keras_3_training/demo.py | 580 +++++++++++++++++- 8 files changed, 569 insertions(+), 11 deletions(-) create mode 100644 _static/demonstration_assets/demo/demo_0aec1f9e-ee6b-4237-8372-32396d5dd85a_1.png create mode 100644 _static/demonstration_assets/demo/demo_20c945e3-08f4-4e92-a98e-4dd6392ae14e_1.png create mode 100644 _static/demonstration_assets/demo/demo_234a1746-683d-4d8b-8674-4681a3b2cff5_1.png create mode 100644 _static/demonstration_assets/demo/demo_23cde79d-79d0-40da-b8f7-a80cfd8d8fc1_1.png create mode 100644 _static/demonstration_assets/demo/demo_78759557-918c-4b68-b975-65e4e9f2d0f4_1.png create mode 100644 _static/demonstration_assets/demo/demo_d716da26-80d9-41f4-ab6a-567bba016fd6_1.png create mode 100644 _static/demonstration_assets/demo/demo_d9dbeff1-fdad-423e-80ef-f7e8439a20f0_1.png diff --git a/_static/demonstration_assets/demo/demo_0aec1f9e-ee6b-4237-8372-32396d5dd85a_1.png b/_static/demonstration_assets/demo/demo_0aec1f9e-ee6b-4237-8372-32396d5dd85a_1.png new file mode 100644 index 0000000000000000000000000000000000000000..a82f1b36b7b90b2fa6771010fc0a74493e00d26b GIT binary patch literal 19068 zcmeIaby(F=w>7$HK}rEpx}>B;q(h~oyF^MF>2453Nh6h~(;m;& zP0z%3`Vic6sA)8Ezio3fU*x=rogl(qG9EY1l1i(JseW6n%xZeKT760OvsYM!#U~c2 zDxU{8TXT5I+3+N>NKK?v9{)bStn&TI&vLE9-V8taXx&Bsi-6P8&xiC6oRZF_j$GD> zB~>0tNJwPRDq3M7|DxU>KSy}uM&uG#Gc6q*U5`Z&XFBOothwnXg`nMO3@idQ@za;fM+z9daN31vtdLImHRr>r4rxe<0LL+!o zJeqDfn5A9qxTtW(_PmM^kDT{SLIU}dCr>_n{`_pWnFyZVQfe6xm5>m|YcpZ^`+H#K z#MM}d@k)O>7L~`sjg0JUGv?DZF2NxokDouMINY9%fY;Cn3sac) zCYB6$)4({f((?;Cd0hl4d9BC%_3J%JYWp%|qINbbXa6+4LR+%AI6V+oRK&kYM1+E% zrKPo)Z4kM|rj0-_v$07S8q!EbP`&SrVw&84h@2N=dleyn^30438aB3{R=V!){B7^a zu@D`7ebUdKk-acB?l}??5TIpa!)0M%X{F-hq#P+JDRZ%B6r$c$ zWAkn8?ehnt29+{Dk*ig$n8^RCcDteWhtCFUb4kqDnD&n!KjQ7k-%AY@8&ESTCi({j z1%*b!s;O9hl4nH6#7xsHd{#LNaKNeRYe!!WtBwvs}H8u6AqGH8FY;^RSU%!+Q{`vX$0$#r^ zJY<16b95~ApoV#;OJL-WaJq4sEwj4B1I)#>iBTdZg)VA^EW)CecEmI zJ%QLc;qTwSmwZkSH^aV}_q~`Xv*fysdkqKY$%_|%6euG?f`fC`^2!AH4}X|&HxCXH zNu@{nAh$P#gpP0HW*z}@|EowE{!d56|8h9}U-H6i)tiZd*GbciTHe|_If*}gdQDYL zEo{2-NhlP$CL-|_USr?)YfpV=H8 z90WESR&CC!si`^4HKE{A3JzI$Nl2h;7VE!XSTOmSC_p6Swk2`2HLaqoY~MZqqrJUl zaB#2=!ZG$r5mX_3%PDI zrALT*4mv=dxj{r^++f4~DSiP`iDkTku6a&yOWO_(reIiFwHp$Biol*RLm%`^47u)=1ur7|?yws8;L`1JM<>Fop=V|ae zuMkSTyZvfv>bZopG#Vl;Esc}22XyYubH zhcP94--Pv-ba$^RG(d)#03zY2d3Caqv-gWg7#tbRhFmnv%-2FfLYBhY@7%d#?^xRV zEu2CifZF#MW!C3tTA|&^)wQj-@iJ5+Uz3)J2@3@UWo4w0e8znqZ{#~NX(0R+Rfr`w zX;>3FO#-sBnchj!pUuMr2n3g&`#N#3!jJqHr`rD|Vfa6C0RPVqmGy43PL(|^8L#y< zK0dxTZ{E;6d{{o_qgC`AJ4w;JysEldXEU7{Crcp#)6dVZs}PzA^l-^}ar^uGEyhdl-n@BJx!%)-&7h%f ztQ{c5@yW?2QUe2nJ)h7t!>y_6cy3diFW!G;%50~}^0kWFa@DeThP4dFPZyE98|^Eg ziR9#nvNE1Gfq^<-esy9)0(dGT!*b|=JfL1<;t}4y9gN(7SOio&vu+z>_iy83<{Nu@ zdeSj4WQ-Xv{r7`E|JH59&Oc*+(Ri!=NFN4BVc>W1Q1o<#p7_?SG~@PgeviKlhvlXi{Gv$NEQ zUpXrDFt<9CgC^WYusE5bjL4TKgcT;!z@g)dR-y7?MC}6f5#;fz~ZN|HO{*7g@(d0zoK~me--!s&xhUrF4F!# z`^5j^gm+sV$aFvc9rSx~5rdPH69Q?-B)>y*LOg4p2TYr2iN$e<;JpdBgH4T_Jy z{iC(j=<4#qVlWF28Q4odKk05Y8n@%`Z_iRsR@zC+%Hp7+q4gK)@|l>KJ=-L`apTos zwo*czlGw%22xlM?Y`uXr-5RSzc=Kk!;pU`qa{vay|NHkRa9qBCvnyWMM|ZclzW$e!icXU!fZ!p^3kwT(U8ccX=L`S}cK7s* zT|1i0V&^|OIe984C$6m>zlfcbNOK?&shF5 z4>lY{nlh^YH6$?p(bo3eg6DG_5tCx{zd-^gC^bITT^vp#PjJ_bF%(G7dxyRkr=Lc3 z%dG-oy3=K%7@;!4qVn1APY6#RT~8fdzjqgTEUPB}SBL=0zh;Q{_uCC;_=UQa91gQk z!L&e5k&d9MpN|Iw+_C##!hsjmd@o1X>UP`6AtsTz!1hd-N$JavlfC8N+uH`T4#?`w z_}dBBE^Gugy&7VGFW*zegSY@kL15Ae3X&lRA=^QnvUmBwy!XdYu3Cxri6f8o7$L+u z{Dy>#ig;e*4Cp|<3AV(W`Su7%T6dI0PZ&P)+Jx@x*#G-EvHg0m(#{}J#Ou~zmcr|1 z!$-=>giyxyP)0o9=AK(H>HKz!Q~w>HOW}+CLCyA%u&|bw#r2rx=H}<8>&4pTRwRy& zj)a7S{(wn-zvr7V6eEYJLsiChvENY9Y3oOR7@w~Rg2*f(VfFum{(pdexv8tYJ~8RN zrJmnQOK+i=G=jfs9^DHKKw>mFMYguKu3f+0lI(YB*&T=E6!3p6VfWU!tP=uCtg7PQ zm~EuCnJ9YJZMMY0MSg0UtgPmf6ixUBAVKK)}&U$HhgME*;SZ zaMf<2(QdY(qo=180L;%xD({`g=olDKi=c$2(Ln7D8?3#Xvk@1;MfM&7x3w$gUVpJc zV?y|+xVWIYI^jK=sLnS{O^nBapX4F_*@m|(A<+Kr?M24MzWw@@ ztD8vhU)6u5x4f)u=*P-;=hLn2GN(Ns-2jbnf;54i-%nS z;@rl4t1H@O$Q51X*WYL=U}kO2_D(9@LjX>?YJCDE`obhFN6W#5Cv2er+vuZqAT_i7BCy1m+Dw4XS$>yM;v#jQvA& zw8=~()Y4E+e4A+hCoE{Cih{@F-AXV>3k?Zr1{S5iNKeRlitWAZ`n}veh`R2Dr_5U_q(aG*SMt#BNrUO{Wwc0VfHU?G%6A$x?#FNWmSl zhgNR@zad$Xde4K>gYJJ+`-Xey*|3WIUWaT*82Y-|9{>{rBjaZwY;zNnrq0fR4?`u! z(%yeNnUG*m-(!)4=j8}4oChRZjaR;VqL5c5U}SFzsKXNzDTMcbi9-eh;wjrN|aDTSDAH76~~ z&JN;>JL<|H*2)N;y^|WdIvwiH(QMP7)~3IUk6bGbd}d}I#c_Pxa9%~7a-*L6v5u|Y zj0{IBER}I!uY^TJjCjbP3`lk5R}V;}Fl@s$oED^^Zz%;VKi7B_!`X#L?mw8#el(ni zvA=;W{{^?qN@JClNJA$uuxX^Q>3d+)Qjg;cR0JLwLP-RJf*-Ll!(J%JXEk|CyOGLb z?CXFyBbHk1tQ{l2y#0ozV)%2#HAj25tT zDrv{F{wC63VNyg)*P>w&JgW!{LZg>PjS$1<7wWD!_db!|9z)rf!amrK{`{KMKyIa{F9>dv_H>xjVA~afz=5zK;v{<(i^n;xq{N_U*@=kFmo=lVV*+9gj!wbn;8o-fa#p!wTOW(+!b)|4$g zuhqQYo|V^{g{|o7%1cL24_odPWEkU)542UztEwSq5mX{%z~*oQ#i`Qh>+|l;15Bu^ zti+NO-3CitH=pS?5SP+QMqYAvc5?q^xqsstcJA2nZ9(*hq@@0K9a z+AGLr_G?2dKR-Rd!osI$g7YOZK0cUM{OwfPb|}d``_p~Py@7l!PV$DErI`_86+BLM zGq}zh775E9f3O7x(fyBR(0FajdiDq{$4Lzk^KG4{x^^g)$DT!mJ)cqXH9JqIs+*RR zCzm{)wh)t_0NF``=$fhb>Y1#t9#4aE`R;=U--g;xHoo-{Llw9PQ?q1)hz8<4T8KBj zKL?@wPRh7C;xP$5leCJr4+(~H^qmK+%L481qn7vnsz&==<|ZyP3;+{N+&ZZ%DwZzA zB5Rbone6l(-J{|2Lz2ZEqe(I9@k-!OCJQi_?%u^He>o~uOD?Di)k1S;C$`t&2Frm% zXj@Hfm3ANNeaD5T?p|K$;z9V@4c-FLH{Z7Zi(E6I*7w5e=tAA9#tIIBhb9WBogO`} zLI|I+2~iTojO?#ARXaTjyTwgh$GsHw`99jp)SW74nJ!6t6Ox$No|_bKEuD z@Hk1(#gOYZkb7__A<h8TNr0UFhEyQuf*eHcQ}|o$2B!I zl{V7k**9L|5MNaGBpY}{nby>FE%;GUIVOp56ghs0>xiTjcyUtKaP)<^zW|qm3%tM2XNXm@SRke%}qtzs=7D=z&VaGX*F{_s#Orf~^EW7gj-+wKo+u zlP?A`WRUE)S>G4EjS^t>AYb-$hY&Gs@r%|x2&KC_bscA+pg^~>%jc3&!Ni1wCh2bD zS&?IMa!3C=YR!HV;(_UY=^;~riECJom-fec8pr#ruq>-?FI@1x^D13jKEm+l z?QRMeiB^Z|3yu+QZ|}MJd8f$>AUPtVqFw_70~`bz0zUd%sOR29b778|aMMD&2H*=h zpDmonlQzlsL_{cow*93w@({m#Ih zi2;1n(-)h3EbNb+@A0m`9!!(G4yU@|*jJ;c3JR@T z)3x`lhM4!)M+u-TL-_yvDbHg!Ly?=Ci(sf;1;WtL$qC8C#>+DT$pF8JiHQ-2hLa_7 zZzzNTmLh;<32hA{HEw%%=eCGvS)o3T2G>KuTcqvNl*bznEM+Dt@Z8UAT;^#@`LMB@ zQpF!l*EE$`uC=8NqTJI$h10A(0|Ex! z`$ux`0xC~61Tz%PTEJXX*v%FpsumrAosv{B$?;HD{-FBJOd(V1J(+9*uT319NOFKXRHqoza%G$kMsi=~t@3&tnU#UnUtzuHOOd$5O{SRk@vJl;MNfau-=XuOx zr#U}gYuphx4A~fuzt8Qsc6s(H*)LX<|1eF7`UHRV*JBRd-2um8WjNP*B_(6brUeg# zZjnl7qupZrVATbAmMtcS_3XmUu%AvRS!lH3Bl)AOupEu+PZcmkj~{JXbWq)IPL)k% zj=ez`#h5!;QpKyc+4X5R)1o||v$0o^d9faQ3$s)r8JaQl z?iVgJT*_AS^2tT6JSLp*IE5rZ4DY`o1rgN6wrr-qw+ZS_vI-jihVFOL4%7h9YQ=%4 zjU>=LeA&hP^ikHw<4r!~yfmQ)SzBFt&rLj>OAYeBX}0oJjwKQ9H1zOr3{y;<+Vm|b0h5R=Elv3!?V2CM&nco{1esn>ho-cGdKVp5Mf|&tMSob zv?Nj-bK0Cbr?}e8#Toc7b`OWY?ciPNjq#Iy_d&*ja`;C`lb)MQkg->OykboED?itc zFE(Qc3L(Y^gwm3^ls!qiyC{GDm|P5N>HLl_V|u8uGaZxbWo$uTd^1kF z#vLMxJp0fWEE{}|89zl9SERzxx9Umxy>3App*)>7Pg}B(@8Ycc+7s!~MlnJ%R>~}6 zV;ITM_wgSB3GRWD^}13Ds|St>jrt>a>q+ z&loxtmU|~;Y!P>8xa?C3A}H_0bmm$?lb&QP+0VbYG1i4$(f4SpmfQP0!OlN}0mfVN zAdXX))#c^x9vL;KB;shJgqR{T?=?2QMvg{)20^diC0dz9sw)pkvE=pD2(%7@?+5E^ zZQ{;MBLyg>-(I!7r>u+?mpQ-4yeWW?|IAQcowEi^2uapgLhWkR5iA1Dhr|hy}N4cyhhCBl!eSJu!Bg z#lFZ=hz_X_a);_H zZkTg2wttK>Uv(C;_>h;Xj+2D%e&th*TWN%bbxoZ_-ah9^f!@8n1}J5I6(vpdtA~H( zswUP_ZW<5kEU})t1~_EOr*giBOke+K)(#fHwIJGy#>N;vRocqmp}FR8f$I()^=Cz< zhkyD+)r82>23W{&2}v@ids{32p00C$T1gd;#UIJgleb?9r{t$`8n3EC^oeA<^2Fpn zM(gGp+MO4`joFyZReSUztw1ZVTa7PIPuFT5G2q5=Pp-$JVNa(b3fWV)q$HxKp&u=hDd9@7a^`3t z;p3AFns=K)xjYu1b`K6sXO1QMxMJaAtP&PQ#O2peP^?X>5iXN}eA3CG?kA(kKBXng zygbiC^cuIgH+Da7yYe%hMSsSj5M?u^INetfB0CU1PRTs%Tj@{Cmd!`Lj%Dq+uzkvY zb8cSf+tivGRDdtcBy?VCL!N`ReD(<;@ej8*D_=b;HyPPYQyk4;hnb&JOvdE3i#^Dj znDR+C>tU1pq3?-ZDcvx}6$ACmQYxQdLDBt_s7J1fC+xsQPQtPQM?2l^Z zP?t^en-8tDm+C_zmh1RT#6me*a4DE6ZDcrDNL~2#zaqx!b*J5PUhMWIb^)g4cSO(8 zr*vPsQ=VSJ>2r#R5T=Vs3c@$Vl2V-wA&OnaE)y;>^AJ9ae_pCaETA)=X}Sq;XPRXGx^ko}_uT*@gOG+(Bl4MTfsfiStbS}9>4y(A>)*_~J6IjPVGe&* zSV!J~p68?GUI?v5srMg`nD|Ic1f;NK=N&^D zF@sHLdjR9x3HlTh69%Blm1|vXaPM%V$Jsv6Q5r4M1IbE3EUCVGvsLoDceud7qYdY4 zNdr5>W%M2Wd86-iEN7tYPAAh}yQ@o$muU^9j*cJ0$A%Gt( zzRV*Jt54t~*x8LdIWYUFEJRkL2EBAB*&KG}9y8p%YXoKK7^sJUrtzI@hYAHZTPc~B z^P5=@_pFw(vOfT68Zj|yvCCt6U>*Zw?rFUsYd_Y&iNZc!BG-v{r_Ogv`d0=vB%A`J zc*OZdWzDYgu;b=u)7Y~--W#?a?q0f58ubUacGim_$GJT%iJbMj(nCqvn64eIv+0gy z4^_-o<9_fU%;z}}(a|Nw?YF$HmUS*fB(voB=lRTBbcSq zOVrPzfHn+8WtQDr3RTt4apeh5O?jkJ;~H~RC`VeYpniwf=3}Fffq`VTtHP!Qg}6~_ z+Wy+zZC!h=Sw1f$gON}^mywZ?0!5?6Xi<`fE+`Wc6BEBK^L|V4Ip1yHIXrBGx-J5E z^XCoT<7-ug&&mUUBJ@Bikbr8zrxJGWo?ra8Hl@0#WY2VDB=@sm+rCb0?F#wRN5E*X zJ;*=@DOj&Rn5O@uKJRL-(z>6&gUe$4nu(W;wR1{$n%5M@QpxGlGGs`}A8M zEO)x))R<$g)d0g>apTbYpfXNAtvph+^SaoWMlmk8qJs8Tl(VCJGW-~|f2*8^u#abv zl~Xt6ubtm5H>jw>VWnxgxo;vHyKnKxcSb4C>HcH4D2k1NU@($5PYTB16yq`;yuvErj)C)}~9z+<2tFMz70UBYd#&?VS7`0fc`&nZ>OB+wr5#>)uC{oCg9Q zxhv<6kB?J4_LlB)6crUgLsk-$XafTSKO?3qY{)@|(IX>g34%8?*vl6v2w-N9LAO@$ zF*_rJ0je`(QEBQUpf2lo$Y?V>p>vjLp(Aw zWBkl#XKNdIvR>S{(?)Jb1Nxcv2CtxaP=SsbGV*|1jsex&=WQ|9jd;{Y;|)HwY0RM? zBO_Ctx2AM}YY9&qU5^2hm=!umRhIl?pf8DmlJ6e$B|)i0I8JQiTpyv;W$~amd@UevBg8>6d==&Fmw;=-xZ^ zP{y-X^dMPvWtyZHDB)j9F$@gm){9LW@-i3<&)v)E>7?D}S6K42`CJ8OO3g^Qk31&Q}A%7t&N23v#3TX9!2YShKDFYc`^ zpciPP?5{J$C3Ic_OuI><_r-JRPSdc)w07Bs-xb=QdDDyXQGcVBv2z7eEoYBBFE;@e z+R2q+ImY`gE-^P=ig+SOIrAtUgn)LhWsLfbdR~{8>fw}YK3peY|2M;1 zU#sB5gg^!rBfqnpfiKH9y-KUQ*EJg2-Y?31GnQ_$K>z;TL_gKIeJK3hhJxzH{i_@; zrAx(}zZjUPS4V!_PG^DFo93GTj!#!AZFb~AspKrTQD8y6HiFS`MFsRF>tK=IJ@XJE zMudQXm9+F>nnjuMY(wJaGliL%j|%{aHN_SOJ?3QFM&X%;27c%rR|OUI5NNUC);in)<}PJu=kT zKNxLV?EbjpicHb!3^b3jT*s=3ET(~irkCo-EQqAfRHA&TH+~C{EVf+zBxjOhXw}8v z>!32>9&YJRZyL%4T`|IK8UyzZollNDV|jXJm$I`5E4S*UXh(zd7H&o+%HDFfNlmsA zTG%Za0~Rqc!(TLp^HOtNB<1wORK1M7<39>11Q`m^I5@Z^#iwhmq$TcFyQ@5#0!-Q< zSX~`^_WXIqa5jK+)y9TJ15Qr-D>rBJ-luYpAHRBjvwE4ToIdU2!lKM{A_QpZpZ9?% z`5I_s4?i|z0BNCGwzbT(XISE=G49*=X^yM8gxe+W`4${Ry{sJ$3yUbVt}>;2o0Uz# zpVT!fV!35qKy_gt5M`!!neigH&8N6&kyA%3DVpqeg?D}s z$0oV%cg5b)+nwg=Vp+dALm3?Wp?mb1Wua++6S=&-E&E-`HQu2DY!lPn0=@3Pe;4HT zG`n-vMhwa-$-;e)rkkRaWJ?D#!+46-76m(^zFb8P?*Ks9tf5%fGiX zAlG!S7;&yW9-H>Gxn1lMp;?HUmNpa~yrJRSpe~^#AH98O;1{#$DZ*t%Oh~@2{aVI7 zWQ{j|Zpym**4%FUBJS@Ymcx!y7=gg#m+s&;y z@p*pi#Er=Gr!tWlP%c+*{-zDYHt>|GVH&Fiiv4BAG-c1b4crUUouq@W9BqO~ku#VI$X@8+U&^&vM^-Aa4!eLj;qtPN&hw*;sKo;oGUx}Wvy-ZMU%~6xK z`o_T6#hgD~n`s7s&`NsgIFv(4Wbd8x24juda2^T~W5@Dv_x0mdrq%hyl~(#sbLtMC zKK+%(vuD86sQb#b`ey2D>d*)_Ud*j)tjYJZGXAnR0Z7}W^1=m{@mbl2*|(6Lu*E4! zh!R8k9ebeD%F`X?$@#j@jq%8yswk#~LPb_K zx4*&SW!VuKRSZd9Cfqgl6%VvZ?*e>(3iG{OpSB(h6^o z#~6)spSU$4Hlt^l&gOs9x)kYqNZR&&Lc^t8b5GXtmB{-#U2FQJjqIU+cCsc6M9|HR zMViYcZz?>ZvS4uYVlN_dMA^BlrsTt+fB*`#+mdz6%~!pJEk91E#Nff_D9Aa;vNLFB z-jN9J>y!yaWn}2steio;#i_~1C=HV8#K%|5V%&csbGY}n?JNZnMRadTH=}2Vg5n0K zYn|mqmOSe|F@n0wnt}+jyK17!yEr&(7qM?~Rrqe@d1B(1G4K*G2`W+x!lxZtPL|tD z@Y*smmsD+sEd14IptIgUjD=RI^6=t_TdgdPbf@tuml97ylCzO5={?HHlQzFjLV*~(iqgUUQr38xvC%LuH1ZV!Z5d#Wy_}S(v!&2cn1SxpxkJb zqnw`g4t&Oy2r++Z-w&6B1wP_djt-HkyOe^6@p103`sNgP`Qntooqp7vzr?w6*=VtB zkpjPp^ke+|P9!&TLfg@^ah_GHUcL`TprWed1zvk-g-GC)xp+U{ZmM|}Mmikc3WXNH^v&b zZQF8W7qZ}ReKNBEmPSveoIlKFtufZG%&>_YH>M}z?>C*Fe0;M#OTc4gQoD@oU-Ue)2VFms#&B{( zLx9-V-qGLo|Xy;O5NC%*wA+v{+7q@Q;7XK>f zb4f-%lVWGVT-&L->gS4-K%gDApPW0%*use_=myc5_NV6)+M72WCN+J+^ij?eXbE@u zLBa;^gKrZ{fBpnOm#;~=%OM4L1@W+3?7wymVpg)0lEeE%&v2njyfR(Or|-Qh`7>Fp zaqK^N0xmEF_!SFe!ZXYiPF}gWVO7}1c;x4OPLo=1eyx)qx{;8duijRyhBa165F7%t zq_Tu={&%6X)fJuN88~UY+h6HbJ7Oe*8irlhE=w^fR53{~73M#l$MPnlVq)<4UHK0; z$jPs#_gmt z+BYm@b)=ZC?Vdv*Zi_y@>GH5T`T#{#RCI?$GNKXuM<6stR;A6&%?MBhRHTBu8H)Bw zpYu}F?wub*O3`c8;6OquSnKuu&PbIpD8=x-cLNJ14|)Y|7}|#qNow7; z+d#+u3u)N6miOP>F{vLynb9~pyR4($)9O0? z9>{GTqq#A~l9W3=<*$-H1OwW=-`vkEi8A9MJtF=7<0+rSvtNbeP=XW?P~o2ElLfZ0 zTGihXzJ09qUY(8=i;NArG+dkS_p>2S9*Vb0yuNQ^D85yri9QE14b?A zt({dky6W#Rj~p8BL>Y?N&kqL<^ZsI2>JJkgyE_n~7wRKS^2&U-=-7<{(jRZvGhH2S zU{5(kz*~%imks6WgaCv|I~(5~`8G`4h?xfM$HDqv!HIIfEp$LRr{6d4L(SLfK0V$A z5MuIDQBn0|v|G;XzVCqsoSz9B)^|?vE$qgGA}8YKEPjH;UWWw?0)c@L*QIymGTth^#yqdP zIgUz1@&5QX)*asbK`7JYX3^8~e)QmJSN3wRyQ};b_Chw0T=ELq+faw|a>)rIZ|#~Z zqv7EA_otisbP$IBNXg6ha58L0)k)^Ycq*g9_vO!zAHXS~0wxd-%7cD4FYEySyK+#> zL+4OVk1#FI>K9FlpP$V6*%6kb<4}0V#7N=5z`_DL0|TNL$a9Gd1$ldlsgpg&EF8oj zbU8(VEY9FfQoI?|_WqUMWl3=^MbYyT=eJZ3FfArEp-EBp4m6RClAs6igPDAn;g=Uh?k!kah2);5S6Mqrbs~XbrS&^ymu88N;!0j(0 zi#JtF&sOLS+wq&G^`tyXa;PIW?>9Ryd4n7*pN>q-@;jtP)=e0py8^wYaQz8+emAcR6CYi(C>mR^(i$b;^{cWnA+e&g zr@K9Tv(b~=b!hVO_V80_rN{xzxQoBF^K$PX+~T!ipsgmdYqYt8+2G8wZceBX{R%Bp zOn1J`k$WIFD$_Jum{`&blk+nH7!k&D0tt=NC*wa$K0 z$6>(1#GJ2%_UY4IvBMpsRVT1$Hf@ZT+BBo)rpQaI4X}jP|NN%w@gL=&MuW?z5bqcJ zePWKaW(w8L!xe?5mID$EhNNtW!>w}R!)B~Sww+Jr+9%OD+(;Q@V`s^f0X*o%En!x>@2;~iOogq$ws?rlCI3UVfX#D^&Qkggh+N=}ZYHCBVK z=~5`06XU`>bfiLyL&4eoG-YF)~diC?nF z04tm#^7n$IoM(-EJ+QRgEOWn7{Uekz10^KxR@pyZ`fz&bMI)di+Nh}5L?3~;I7I=C z>QG_6YqW$07KlZa?=@GGNJiqfx~zAN)7%&@R?E(a8J^VLd>WjW1%><`lZL4v<=lF{ zE3t$+848M(^dpJPRuy$nD*b;v^=!=^>xGrSK;euuyn?)rW7wEjvKef4bKBblZff!l(#1SFy-u-BPjTH$FZ@>jc&;`8R+w}~_=8Tp=Kx6<$ zYiIxjL67R@ks2`u=%{Xs`3md%o|q#1`z_vpeOMo2A_v1yk#qu5C?}M zN)ys;Rhkj-*b`due!j7t2O$Ypm|pvx+omnHuLG$*B-OuX8UM zyT{ANsuu9T<1fwmkoC|{`jY=bnfK<+7y}2q zfuk@lIpdee$XEaL?t{JzvAVkYR7t5aPiL^s!wD%J{ErI$u__08-?L3V@C4HSQ^Ajl zD6{BC1uZ?&vj?W}60alMcVrK7U}f$NA5?XvjIQ4xC3PFTd%xz+Z2rK1o>Ou!_eN`8 zdGB8-kHM}ptN>ED+3+I0+ODW|=YJT)OXJd*gTdGH@vhtl5Euqgd0qo-{vEu^#=hRr z!UJ(!AQE;fDk>t&Em(jcP}xoVG5v=zwA6zVX*o^nYOBtH`1tobEdJk${`R(pi$h&7 z#x_k)Bc+K;wA;=v;a&&0I3PeVTinyLX5#l)60{PLl4FaZoEsoC?$3}R0xKM?7&O>_ zZM1=DvHz=)ICyDkp#%c0p;eHH+n@RQ{stefp*)T2P&A4c>(|?ZF}}RT@tR%ZMMZT1 z_B7jr2Snh!rdS&(ybfWGG=~F{SG#+Qk)L;eG0xyclRpYviqOW{cpfla>oyPKRt+Jx zT3)b+)aF{6L?G`~0HvijkmdaDJ2W$1+ah4Ror}@ZR}tc={g*eNg9HEUXj>Xogb@C| zY8d-@wh&df1soZCPgj|sFaH}f!$>PNC?kjQdx|!vYe}iScTk>H+7`MpyCNN#D?_O#apB_4ZbnUHz9Ajgo* zEqz*C9f4ew38v7en_L@DprVY9q?i6|M@mf049P{-+q*7QWxNqAW6X%?Bq6sv%FyW= z_uaGsaFX6-VUYosVi>pUhIYAlsZbQ7f+`hsb;?_~jIW2kNBWIEefk6xz>~=e8?dQG zKsRt6OeXNG@DskSa<{W z9_Z2M-2Qa&fb2b@sWJWLJ75r?#QS=BM)wlrtN$>^W-C#{%?1QehqeN>qY(yR?>+LC z3xxkDh?|iuQRq$1EiMMaHl`C2(p_BH2uovz=qA($_O|ThsN$wQMOS~i^nJfeZ{e%+ zJ*%L!8jvujLg+O#G-yux`1lxc-Bu6nx&*(@3vkpTrygl?fzG2G+{e&*d2x1wnz|Sk zW^=pos_jo}7}9TxY{){=Z-R=`Rv`beDWEAS5c|GW%)aNqvc`mhC~i1q^*q{o4{o3W z?Gmts;3LVXuHrPu=)?NYVt%4fP6mSjp_yc+FHb_4(5oOln19f_EX$%ONJ32bPng+Y3mu(M&g80M7kB);G z)^N&+o0w!+jr*ChZhC1ffNsHkAyOK7R)W3gM_a!Drk$M~zmO39y?fnOUYeS>fTe)h zYk+)+13UgZtOFeHZuykqda`nINToFLo|^bd5@KRQ*tXhV4N*YtF}JZ%@Z!y33nbh% zK7tQ2rH$u*zt#`<9T%=4w;uw$8l(0=?D1IqY8o+`2pnk8 z0;^n0f?b1UgAW)2fCV&$3t_HdW0(B%Q<2cH^F;{%4X)Ps)fs|Uf|&bn-mLEXNK+e_ zv(lyBtMc&+fgK-tF%2kW3_&CeDJOD*cDzuU+K0ID;xJ*jVr9hwscD8#c;V>u8t9*C zb=$cJu}kGmz6jWmpdA2ps{SLmGY9F#O?iy=^NR>M;umb*nDlR)2kWDiHF`V`yy5lk znkSqPJ}VRD_sDoG{NehbxsCB}OJ`%UDB!I&gj-iYu>u!XtoTl%OA< zR!Z%2cGx=p&HTR21P`dUk!HX9_jj|x+cl;k7EnMC=2||{@S-(zQlIrMt0Lsn5~CJO zq_hRh>&WcA)SDCrW)6)%{VeC246w=H|8DhyFaO8xhPb3=p2TuD@V IPYeV8AKE)XjsO4v literal 0 HcmV?d00001 diff --git a/_static/demonstration_assets/demo/demo_20c945e3-08f4-4e92-a98e-4dd6392ae14e_1.png b/_static/demonstration_assets/demo/demo_20c945e3-08f4-4e92-a98e-4dd6392ae14e_1.png new file mode 100644 index 0000000000000000000000000000000000000000..1a9ca2e9cb060d7e72177820150a9f96aaf3b880 GIT binary patch literal 7726 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 Lef4a$qv!q)(@Ds1 literal 0 HcmV?d00001 diff --git a/_static/demonstration_assets/demo/demo_23cde79d-79d0-40da-b8f7-a80cfd8d8fc1_1.png b/_static/demonstration_assets/demo/demo_23cde79d-79d0-40da-b8f7-a80cfd8d8fc1_1.png new file mode 100644 index 0000000000000000000000000000000000000000..1e1f96941fe491c2eda810a4cea6509ac3c7f859 GIT binary patch literal 21158 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 Lef4a$qv!q)(@Ds1 literal 0 HcmV?d00001 diff --git a/_static/demonstration_assets/demo/demo_d9dbeff1-fdad-423e-80ef-f7e8439a20f0_1.png b/_static/demonstration_assets/demo/demo_d9dbeff1-fdad-423e-80ef-f7e8439a20f0_1.png new file mode 100644 index 0000000000000000000000000000000000000000..d69beaf3fbd0347488f72e7d625893c2bef2bd4f GIT binary patch literal 25196 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 demo. +# +# In case its not installed already, go ahead and install keras and tensorflow. By default the pip +# package contains Keras 3. For further instructions you can look as +# `this `__ page. Remember to install CUDA enabled versions if +# you want GPU support. +# + +! pip install keras tensorflow + +###################################################################### +# We start by selecting our Keras 3 backend using the ‘*KERAS_BACKEND*’ environment variable. +# + +import os +os.environ["KERAS_BACKEND"] = "tensorflow" # This can be either JAX, tensorflow, or torch. (tensorflow by default) + +###################################################################### +# We can now import keras alongside its key modules ``ops``. We then print the current backend to +# verify if everthing loaded correctly. +# + +import keras +from keras import ops +print(f"Keras backend: {keras.backend.backend()}") + +###################################################################### +# \**In order to ensure numerical stability with quantum circuits set the backend to use ``float64`` +# + +keras.backend.set_floatx('float64') + +###################################################################### +# Importing the supporting packages of numpy and matplotlib, alongside pennylane. +# +# *NOTE: Remember to install pennylane with cuda for GPU support* +# + +import pennylane as qml +import numpy as np +import matplotlib.pyplot as plt + +###################################################################### +# Goal of this demonstration +# -------------------------- +# +# The main goal of this demo is to allow people to integrate pennylane circuits into their existing +# code bases. The Keras Layer created here will fully support models saving/loading, training and +# everything else you normally expect from a Keras Layer. Additionally, they will be entirely self +# contained, not requiring Qnodes to be defined externally. +# +# In order to get better background on the concepts employed in this demo, here are some helpful +# additional resources: \* `Keras custom layer +# documentation `__ \* +# `Pennylane QNode +# documentation `__ \* `Keras 3 +# Pytorch Example `__ +# + +###################################################################### +# Setting up the target dataset +# ----------------------------- +# +# Similiar to the [fourier series +# demo]((https://pennylane.ai/qml/demos/tutorial_expressivity_fourier_series) mentioned in the +# introduction, we first define a (classical) target function which will be used as a “ground truth” +# that the quantum model has to fit. The target function is constructed as a Fourier series of a +# specific degree. +# + +degree = 1 # degree of the target function +scaling = 1 # scaling of the data +coeffs = [0.15 + 0.15j] * degree # coefficients of non-zero frequencies +coeff0 = 0.1 # coefficient of zero frequency + + +def target_function(x): + """Generate a truncated Fourier series, where the data gets re-scaled.""" + res = coeff0 + for idx, coeff in enumerate(coeffs): + exponent = np.complex128(scaling * (idx + 1) * x * 1j) + conj_coeff = np.conjugate(coeff) + res += coeff * np.exp(exponent) + conj_coeff * np.exp(-exponent) + return np.real(res) + +###################################################################### +# Plotting the ground truth we get +# + +x = np.linspace(-6, 6, 70) +target_y = np.array([target_function(x_) for x_ in x]) + +plt.plot(x, target_y, c="black") +plt.scatter(x, target_y, facecolor="white", edgecolor="black") +plt.ylim(-1, 1) +plt.show() + +###################################################################### +# Defining the Quantum Model +# -------------------------- +# +# We first define the quantum model outside the Keras layer for the sake of clarity. We will later +# encapsulate the entire circuit into our custom Keras Layer. +# +# **Note**\ *: While we are using the ``lightning.qubit`` backend here as an example, this has been +# tested to work with the ``lightning.gpu`` and ``default.qubit`` backends as well* +# + +dev = qml.device("lightning.qubit", wires=1) # Define the device for circuit execution + +###################################################################### +# The quantum model consists of a set of repeated trainable unitaries :math:`W(\theta)` and data +# encodings via the :math:`S(x)` function. Additionally, a ``scaling`` parameter is used to change the +# period of the final learned function w.r.t the input data :math:`x`. +# + +scaling = 1.0 + +def S(x): + """Data-encoding circuit block.""" + qml.RX(scaling * x, wires=0) + + +def W(theta): + """Trainable circuit block.""" + qml.Rot(theta[0], theta[1], theta[2], wires=0) + + +@qml.qnode(dev) +def serial_quantum_model(weights, x): + + for theta in weights[:-1]: + W(theta) + S(x) + + # (L+1)'th unitary + W(weights[-1]) + + return qml.expval(qml.PauliZ(wires=0)) + +###################################################################### +# We can now define numpy arrays for the weights and input to draw the circuit in terms of the number +# of layers (or number of repetitions). +# + +layers = 2 +weights = ( + 2 * np.pi * np.random.random(size=(layers + 1, 3)) +) # some random initial weights + +###################################################################### +# Drawing our the quantum circuit for our model +# + +qml.draw_mpl(serial_quantum_model)(weights,1) + +###################################################################### +# Plotting the output of this random circuit, we get +# + +x = np.linspace(-6, 6, 70) +random_quantum_model_y = [serial_quantum_model(weights, x_) for x_ in x] + +plt.plot(x, random_quantum_model_y, c="blue") +plt.ylim(-1, 1) +plt.show() + +###################################################################### +# Wrapping the QNode in a Keras Layer +# =================================== +# +# You can refer to the full tutorial on creating custom keras layer +# `here `__. We will now create +# a custom keras layer can wrap the quantum circuit and its trainable weights. When doing this we have +# to keep in mind the following Keras 3 specifics: 1. Do not use any ``tf.xx`` functions, and only use +# the native ``ops.xxx`` package. For example use ``ops.sum()`` rather than ``tf.reduce_sum`` or +# ``torch.sum``. 2. Do not create ``tf.constant`` or ``tf.variables``, rather use the +# ``self.add_weight`` method. 3. You need to pass the weights as arguments to the QNode and not use +# ``self.weight`` inside the QNode. +# + +###################################################################### +# **In order to fully support model saving, we need to import the following keras functions and mark +# the layer as serializable**. More details about these functions can be `found +# here `__. +# + +from keras.utils import register_keras_serializable +from keras.saving import serialize_keras_object, deserialize_keras_object + +###################################################################### +# Methods to implement for a custom layer +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# To create a fully functional keras layer, the following methods **need to be implemented** +# +# 1. ``__init__`` method: +# ^^^^^^^^^^^^^^^^^^^^^^^ +# +# The ``__init__`` method is used to accomplish the following: \* Create the instance variables that +# define the QNode such as number of wires, circuit backend, etc. \* Create the instance variables +# that define the circuit such as number of layers \* Select the pennylane interface based on keras +# backend to be [“tf”,“torch” or “Jax”] \* Call the ``super().__init__(**kwargs)`` to pass generic +# layer properties such as ‘*name*’ to the parent class ``__init__`` function. +# +# 2. ``build()`` method: +# ^^^^^^^^^^^^^^^^^^^^^^ +# +# The ``build()`` method is used to instantiate the model weights at runtime based on input_shape. +# This can be used to create dynamic circuits with qubits equal to the number of input variables. +# However in this demo we are ignoring the input shape. Weights are created using the ``add_weight`` +# method, which you can read more about +# `here `__. +# +# **Note: DO NOT** *apply any operations on the created weight here as it will cause issues with +# gradients* +# +# 3. ``compute_output_shape()`` method: +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# This method is required in order to support model.summary() and model.save() functionality. This can +# be trivially implemented by passing an output_shape parameter in the ``__init__`` method similar to +# the depricated ``qml.KerasLayer``, or we can implement circuit specific logic. In this example, our +# circuit outputs a single expectation value per input variable, therefore the output shape is +# ‘**(batch,num_of_wires)**’ +# +# 4. ``QNode`` methods: +# ^^^^^^^^^^^^^^^^^^^^^ +# +# The ‘QNode’ methods consist of 2 sets of methods - 1. **Circuit Definition Methods** : These methods +# create the QNode circuit structure and can be implemented as a single method or a set of methods +# which implement different sub-circuits. Here we use 2 sub-circuit methods ``self.W`` and ``self.S``, +# along with the ``self.serial_quantum_model`` method to define the final structure and returned +# measurements. 2. **Circuit Creation Method** : This method defines the pennylane device and the +# QNode from the circuit definition as an instance variables. +# +# **Note**\ *: Pennylane requires the input to be the last argument to properly work with batched +# inputs* +# +# 5. ``call()`` method: +# ^^^^^^^^^^^^^^^^^^^^^ +# +# The ``call()`` methods needs to call the Qnode with the weight variable. Additional pre-processing +# can be applied before calling the circuit as well, for example we apply input variable scaling +# outside the circuit to take advantage of efficient vectorized execution for batched input. Depending +# on your specific model, we can also include pre-processing steps such as input scaling. Due to being +# wrapped by the autodiff of the various backends, we will still get valid gradients for these steps. +# +# 6. ``draw_qnode`` method: +# ^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# A Utility methods to plot the QNode circuits +# +# 7. ``get_config`` method: +# ^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# This method needs to be implemented to support ``model.save`` functionality. It creates a +# config(dict) which defines the configuration of the current layer. +# +# **Note**: While scalar data such as ``int``, ``float`` and ``str``, do not need the +# ``serialize_keras_object`` function, it is typically good practice to wrap all the parameters using +# this method. +# +# 8. ``from_config`` method: +# ^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# This method needs to be implemented to support ``model.save`` functionality. It defines how to +# create an instance of the layer from a configuration. +# + +@register_keras_serializable(package="QKeras", name="QKerasLayer") +class QKerasLayer(keras.layers.Layer): + def __init__(self,layers:int, + scaling:float=1.0, + circ_backend="lightning.qubit", + circ_grad_method="adjoint", + num_wires:int=1, + **kwargs): + """A Keras Layer wrapping a pennylane Q-Node. + Args: + layers (int): Number of layers in the DR Model. + circ_backend (str): Backend for the quantum circuit. Defaults to 'lightning.qubit' + circ_grad_method (str): Gradient method for the quantum circuit. Defaults to 'adjoint' + num_wires (int): Number of wires to iniatize the qml.device. Defaults to 1. + scaling (float): Scaling factor for the input data. Defaults to 1.0 + **kwargs: Additional keyword arguments for the keras Layer class such as 'name'. + """ + super().__init__(**kwargs) # Passing the keyword arguments to the parent class + # Defining the circuit parameters + self.layers = layers + self.scaling = scaling + self.circ_backend = circ_backend + self.circ_grad_method = circ_grad_method + self.num_wires = num_wires + + # Define Keras Layer flags + self.is_built : bool = False + + # Selecting the Pennylane interface based on keras backend + if(keras.config.backend() =="torch"): + self.interface = "torch" + elif(keras.config.backend() =="tensorflow"): + self.interface = "tf" + elif(keras.config.backend() =="jax"): + self.interface = "jax" + + def build(self,input_shape): + """ Initialized the layer weights based on input_shape + Args:s + input_shape [tuple]: The shape of the input + """ + # Save input_shape without batch to be used later for the draw_circuit function + self.input_shape = input_shape[1:] + + ## We initialize weights in the same way as the numpy array in the previous section. + # Randomly initialize weights to uniform distribution in the a range of [0,2pi) + self.layer_weights = self.add_weight(shape=(self.layers+1,3),initializer = keras.initializers.random_uniform(minval=0,maxval=2*np.pi), + trainable=True) + + # Create Quantum Circuit + self.create_circuit() + + # Set the layer as built + self.is_built = True + + + def compute_output_shape(self,input_shape): + """ Return output shape as a function of the input shape""" + # For this model we return an expectation value per qubit. The '0' index of the input_shape is always the batch, so we return an output shape of (batch, num_wires) + return (input_shape[0],self.num_wires) + + ## We define the subcircuit functions for the circuit. + def S(self,x): + """Data-encoding circuit block.""" + # Use the [:,0] syntax for batch support + qml.RX(x[:,0], wires=0) + + def W(self,theta): + """Trainable circuit block.""" + qml.Rot(theta[0], theta[1], theta[2], wires=0) + + ## Define the QNode code as a class method **without qml.qnode decorator** + def serial_quantum_model(self,weights, x): + """ Data Re-Uploading QML model""" + for theta in weights[:-1]: + self.W(theta) + self.S(x) + + # (L+1)'th unitary + self.W(weights[-1]) + + return qml.expval(qml.PauliZ(wires=0)) + + def create_circuit(self): + """ Creates the pennylane device and QNode""" + dev = qml.device(self.circ_backend, wires = self.num_wires) + self.circuit = qml.QNode(self.serial_quantum_model, dev, diff_method=self.circ_grad_method, interface=self.interface) + + def call(self,inputs): + """Defines the forward pass of the layer """ + ## We need to prevent the layer from being called before the weights and circuit are built + if (not self.is_built): + raise Exception("Layer not built") from None + + # We multiply the input with the scaling factor outside the circuit for optimized vector execution. + x = ops.multiply(self.scaling, inputs) + # We call the circuit with the weight variables. + out = self.circuit(self.layer_weights, x) + return out + + def draw_qnode(self): + """ Draw the layer circuit""" + ## We want to raise an exception if this function is called before our QNode is created + if (not self.is_built): + raise Exception("Layer not built") from None + ## Create a random input using the input_shape defined earlier with a single batch dim + x = ops.expand_dims(keras.random.uniform(shape = self.input_shape),0) + qml.draw_mpl(self.circuit)(self.layer_weights, x) + + def get_config(self): + """ Create layer config for layer saving""" + ## Load the basic config parameters of the keras.layer parent class + base_config = super(QKerasLayer, self).get_config() + ## Create a custom configuration for the instance variables unique to the QNode + config = { + "layers": serialize_keras_object(self.layers), + "scaling": serialize_keras_object(self.scaling), + "circ_backend": serialize_keras_object(self.circ_backend), + "circ_grad_method": serialize_keras_object(self.circ_grad_method), + "num_wires": serialize_keras_object(self.num_wires), + } + return {**base_config, **config} + + @classmethod # Note that this needs to be a class function and not an instance method + def from_config(cls, config): + """ Create an instance of layer from config""" + # The cls argument is the specific layer config and the config object contains general keras.layer arguments + layers = deserialize_keras_object(config.pop("layers")) + scaling = deserialize_keras_object(config.pop("scaling")) + circ_backend = deserialize_keras_object(config.pop("circ_backend")) + circ_grad_method = deserialize_keras_object(config.pop("circ_grad_method")) + num_wires = deserialize_keras_object(config.pop("num_wires")) + # Call the init function of the layer from the config + return cls(layers=layers, + scaling= scaling, + circ_backend=circ_backend, + circ_grad_method=circ_grad_method, + num_wires = num_wires, + **config) + + +###################################################################### +# We can now test out our layer class by initializing it using the same arguments as the previous +# section +# + +layers = 2 + +keras_layer = QKerasLayer(layers = layers, + scaling=1.0, + circ_backend="lightning.qubit", + num_wires=1, + name="QuantumLayer") + +###################################################################### +# Integrating the layer in a Keras Model +# -------------------------------------- +# +# In order to test the layer functionality, let’s integrate it into a simple keras model +# + +# Simple univariate input layer +inp = keras.layers.Input(shape=(1,)) +out = keras_layer(inp) +model = keras.models.Model(inputs=inp,outputs=out,name="QuantumModel") + +###################################################################### +# Lets look at the model summary. We can verify if everything looks correct based on - \* The number +# of trainable parameters - Since our weights are of the shape (layers+1,3), we can expect a shape of +# (2+1,3) = (3,3) = 9 parameters \* The name of the layer matching what we passed in the instantiation +# + +model.summary() + +###################################################################### +# Plotting inner QNode +# ~~~~~~~~~~~~~~~~~~~~ +# +# Integrating the layer into a model and calling the ``model.summary()`` function also calls the +# ``layer.build`` function. Therefore our circuit,weights and device should be instantiated. We can +# verify this calling our ``draw_qnode`` helper function to see the circuit plot. +# + +keras_layer.draw_qnode() + +###################################################################### +# Test forward pass +# ~~~~~~~~~~~~~~~~~ +# +# Similar to earlier, lets test our layer inference by calling the model with the random weights and +# plotting the outputs. +# +# **Note:** *When using the torch backend, we might need to call the .to(‘cpu’) on the model +# predictions before we can plot them if the system has a GPU* +# + +x = np.linspace(-6, 6, 70) +random_quantum_model_y = model(x) +# Uncomment the following when using the torch backend +# random_quantum_model_y = random_quantum_model_y.to('cpu').detach().numpy() + +plt.plot(x, random_quantum_model_y, c="blue") +plt.ylim(-1, 1) +plt.show() + +###################################################################### +# Model Training +# -------------- +# +# We can now train the model using the normal keras training functions of ``model.compile`` and +# ``model.fit``. +# +# We will first compile the model with the ``mean_squared_error`` loss function and the ``Adam`` +# optimizer +# + +model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.03), + loss = keras.losses.mean_squared_error,run_eagerly=True) + +###################################################################### +# We can then train the model with the ``model.fit`` function for x and target_y. +# + +model.fit(x=x,y=target_y, + epochs=30) + +###################################################################### +# Plotting the outputs of the trained model against the ground truth +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# The model should train relatively fast. If your loss is :math:`<10^{-2}`, the fit should be very +# good +# + +predictions = model(x) +## Uncomment the following line for the torch backend +# predictions = predictions.to('cpu').detach().numpy() + +plt.plot(x, target_y, c="black") +plt.scatter(x, target_y, facecolor="white", edgecolor="black") +plt.plot(x, predictions, c="blue") +plt.ylim(-1, 1) +plt.show() + +###################################################################### +# Model Saving and Loading +# ------------------------ +# +# Due to implementing the ``get_config`` and ``from_config`` methods, our model should be fully +# compatible with the ``keras.save`` and ``keras.models.load_model`` methods. +# + +###################################################################### +# Saving +# ~~~~~~ +# +# Lets first save the trained models. It should save with no errors and create a ‘.keras’ file. +# + +model.save("./model.keras") + +###################################################################### +# Loading +# ~~~~~~~ +# +# Now lets test model loading and see if we can get the same inference with the loaded model +# + +model2 = keras.models.load_model("./model.keras") + +model2.summary() + +###################################################################### +# Now plotting the outputs we should see similiar if not identical results +# + +predictions2 = model2(x) +## Uncomment the following line for the torch backend +# predictions2 = predictions2.to('cpu').detach().numpy() -############################################################################### -# -# Add comment blocks to separate code blocks -# +plt.plot(x, target_y, c="black") +plt.scatter(x, target_y, facecolor="white", edgecolor="black") +plt.plot(x, predictions2, c="blue") +plt.ylim(-1, 1) +plt.show() -print("World") \ No newline at end of file +###################################################################### +# Final Notes +# =========== +# +# Try changing the keras backend variable in the beginning of this demo and see how the process works +# with a different backend +# \ No newline at end of file From 6c1280d91d3aafa7bc2f522b7e5466fb2378d6de Mon Sep 17 00:00:00 2001 From: vinayak19th Date: Mon, 22 Sep 2025 00:54:05 -0700 Subject: [PATCH 03/14] moved file to tutorial --- .../keras_3_training/requirements_v1.in | 1 - .../demo.py | 2 +- .../metadata.json | 0 .../model.keras | Bin .../requirements.in | 6 +++++- 5 files changed, 6 insertions(+), 3 deletions(-) delete mode 100644 demonstrations_v2/keras_3_training/requirements_v1.in rename demonstrations_v2/{keras_3_training => tutorial_keras_3_training}/demo.py (99%) rename demonstrations_v2/{keras_3_training => tutorial_keras_3_training}/metadata.json (100%) rename demonstrations_v2/{keras_3_training => tutorial_keras_3_training}/model.keras (100%) rename demonstrations_v2/{keras_3_training => tutorial_keras_3_training}/requirements.in (62%) diff --git a/demonstrations_v2/keras_3_training/requirements_v1.in b/demonstrations_v2/keras_3_training/requirements_v1.in deleted file mode 100644 index 8b13789179..0000000000 --- a/demonstrations_v2/keras_3_training/requirements_v1.in +++ /dev/null @@ -1 +0,0 @@ - diff --git a/demonstrations_v2/keras_3_training/demo.py b/demonstrations_v2/tutorial_keras_3_training/demo.py similarity index 99% rename from demonstrations_v2/keras_3_training/demo.py rename to demonstrations_v2/tutorial_keras_3_training/demo.py index 4a2e5e0983..34682f2a80 100644 --- a/demonstrations_v2/keras_3_training/demo.py +++ b/demonstrations_v2/tutorial_keras_3_training/demo.py @@ -16,7 +16,7 @@ # you want GPU support. # -! pip install keras tensorflow +# ! pip install keras tensorflow ###################################################################### # We start by selecting our Keras 3 backend using the ‘*KERAS_BACKEND*’ environment variable. diff --git a/demonstrations_v2/keras_3_training/metadata.json b/demonstrations_v2/tutorial_keras_3_training/metadata.json similarity index 100% rename from demonstrations_v2/keras_3_training/metadata.json rename to demonstrations_v2/tutorial_keras_3_training/metadata.json diff --git a/demonstrations_v2/keras_3_training/model.keras b/demonstrations_v2/tutorial_keras_3_training/model.keras similarity index 100% rename from demonstrations_v2/keras_3_training/model.keras rename to demonstrations_v2/tutorial_keras_3_training/model.keras diff --git a/demonstrations_v2/keras_3_training/requirements.in b/demonstrations_v2/tutorial_keras_3_training/requirements.in similarity index 62% rename from demonstrations_v2/keras_3_training/requirements.in rename to demonstrations_v2/tutorial_keras_3_training/requirements.in index 66cbab8b67..9fa2186be0 100644 --- a/demonstrations_v2/keras_3_training/requirements.in +++ b/demonstrations_v2/tutorial_keras_3_training/requirements.in @@ -1,2 +1,6 @@ ### Please add any additional requirements that are needed for your demo here ### -### If you are curious about global dependencies, check the `/dependencies` directory ### \ No newline at end of file +### If you are curious about global dependencies, check the `/dependencies` directory ### +keras==3.11.3 +matplotlib==3.10.6 +numpy==2.3.3 +pennylane==0.42.3 \ No newline at end of file From 708898b0cab498c4d0978bb5cf1b7cf08b06d00e Mon Sep 17 00:00:00 2001 From: vinayak19th Date: Mon, 22 Sep 2025 00:54:52 -0700 Subject: [PATCH 04/14] Added torch and tf to install requirements --- demonstrations_v2/tutorial_keras_3_training/requirements.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demonstrations_v2/tutorial_keras_3_training/requirements.in b/demonstrations_v2/tutorial_keras_3_training/requirements.in index 9fa2186be0..7c2fd511d2 100644 --- a/demonstrations_v2/tutorial_keras_3_training/requirements.in +++ b/demonstrations_v2/tutorial_keras_3_training/requirements.in @@ -1,6 +1,6 @@ -### Please add any additional requirements that are needed for your demo here ### -### If you are curious about global dependencies, check the `/dependencies` directory ### keras==3.11.3 matplotlib==3.10.6 numpy==2.3.3 -pennylane==0.42.3 \ No newline at end of file +pennylane==0.42.3 +tensorflow==2.20.0 +torch==2.8.0+cu126 From 66e796502517eddd0f1ce2ce32811adc16fee05f Mon Sep 17 00:00:00 2001 From: vinayak19th Date: Mon, 22 Sep 2025 00:56:38 -0700 Subject: [PATCH 05/14] Ran black reformatting --- .../tutorial_keras_3_training/demo.py | 310 +++++++++--------- 1 file changed, 164 insertions(+), 146 deletions(-) diff --git a/demonstrations_v2/tutorial_keras_3_training/demo.py b/demonstrations_v2/tutorial_keras_3_training/demo.py index 34682f2a80..ba50286e58 100644 --- a/demonstrations_v2/tutorial_keras_3_training/demo.py +++ b/demonstrations_v2/tutorial_keras_3_training/demo.py @@ -9,42 +9,46 @@ # models can we trained using jax, pytorch or tensorflow. This demo will create a Keras 3 # implementation of the ``Data-ReUploading`` models from the `‘Quantum models as Fourier # series’ `__ demo. -# +# # In case its not installed already, go ahead and install keras and tensorflow. By default the pip # package contains Keras 3. For further instructions you can look as # `this `__ page. Remember to install CUDA enabled versions if # you want GPU support. -# +# # ! pip install keras tensorflow ###################################################################### # We start by selecting our Keras 3 backend using the ‘*KERAS_BACKEND*’ environment variable. -# +# import os -os.environ["KERAS_BACKEND"] = "tensorflow" # This can be either JAX, tensorflow, or torch. (tensorflow by default) + +os.environ["KERAS_BACKEND"] = ( + "tensorflow" # This can be either JAX, tensorflow, or torch. (tensorflow by default) +) ###################################################################### # We can now import keras alongside its key modules ``ops``. We then print the current backend to # verify if everthing loaded correctly. -# +# import keras from keras import ops + print(f"Keras backend: {keras.backend.backend()}") ###################################################################### # \**In order to ensure numerical stability with quantum circuits set the backend to use ``float64`` -# +# -keras.backend.set_floatx('float64') +keras.backend.set_floatx("float64") ###################################################################### # Importing the supporting packages of numpy and matplotlib, alongside pennylane. -# +# # *NOTE: Remember to install pennylane with cuda for GPU support* -# +# import pennylane as qml import numpy as np @@ -53,30 +57,30 @@ ###################################################################### # Goal of this demonstration # -------------------------- -# +# # The main goal of this demo is to allow people to integrate pennylane circuits into their existing # code bases. The Keras Layer created here will fully support models saving/loading, training and # everything else you normally expect from a Keras Layer. Additionally, they will be entirely self # contained, not requiring Qnodes to be defined externally. -# +# # In order to get better background on the concepts employed in this demo, here are some helpful # additional resources: \* `Keras custom layer # documentation `__ \* # `Pennylane QNode # documentation `__ \* `Keras 3 # Pytorch Example `__ -# +# ###################################################################### # Setting up the target dataset # ----------------------------- -# +# # Similiar to the [fourier series # demo]((https://pennylane.ai/qml/demos/tutorial_expressivity_fourier_series) mentioned in the # introduction, we first define a (classical) target function which will be used as a “ground truth” # that the quantum model has to fit. The target function is constructed as a Fourier series of a # specific degree. -# +# degree = 1 # degree of the target function scaling = 1 # scaling of the data @@ -93,9 +97,10 @@ def target_function(x): res += coeff * np.exp(exponent) + conj_coeff * np.exp(-exponent) return np.real(res) + ###################################################################### # Plotting the ground truth we get -# +# x = np.linspace(-6, 6, 70) target_y = np.array([target_function(x_) for x_ in x]) @@ -108,24 +113,25 @@ def target_function(x): ###################################################################### # Defining the Quantum Model # -------------------------- -# +# # We first define the quantum model outside the Keras layer for the sake of clarity. We will later # encapsulate the entire circuit into our custom Keras Layer. -# +# # **Note**\ *: While we are using the ``lightning.qubit`` backend here as an example, this has been # tested to work with the ``lightning.gpu`` and ``default.qubit`` backends as well* -# +# -dev = qml.device("lightning.qubit", wires=1) # Define the device for circuit execution +dev = qml.device("lightning.qubit", wires=1) # Define the device for circuit execution ###################################################################### # The quantum model consists of a set of repeated trainable unitaries :math:`W(\theta)` and data # encodings via the :math:`S(x)` function. Additionally, a ``scaling`` parameter is used to change the # period of the final learned function w.r.t the input data :math:`x`. -# +# scaling = 1.0 + def S(x): """Data-encoding circuit block.""" qml.RX(scaling * x, wires=0) @@ -148,25 +154,24 @@ def serial_quantum_model(weights, x): return qml.expval(qml.PauliZ(wires=0)) + ###################################################################### # We can now define numpy arrays for the weights and input to draw the circuit in terms of the number # of layers (or number of repetitions). -# +# -layers = 2 -weights = ( - 2 * np.pi * np.random.random(size=(layers + 1, 3)) -) # some random initial weights +layers = 2 +weights = 2 * np.pi * np.random.random(size=(layers + 1, 3)) # some random initial weights ###################################################################### # Drawing our the quantum circuit for our model -# +# -qml.draw_mpl(serial_quantum_model)(weights,1) +qml.draw_mpl(serial_quantum_model)(weights, 1) ###################################################################### # Plotting the output of this random circuit, we get -# +# x = np.linspace(-6, 6, 70) random_quantum_model_y = [serial_quantum_model(weights, x_) for x_ in x] @@ -178,7 +183,7 @@ def serial_quantum_model(weights, x): ###################################################################### # Wrapping the QNode in a Keras Layer # =================================== -# +# # You can refer to the full tutorial on creating custom keras layer # `here `__. We will now create # a custom keras layer can wrap the quantum circuit and its trainable weights. When doing this we have @@ -187,13 +192,13 @@ def serial_quantum_model(weights, x): # ``torch.sum``. 2. Do not create ``tf.constant`` or ``tf.variables``, rather use the # ``self.add_weight`` method. 3. You need to pass the weights as arguments to the QNode and not use # ``self.weight`` inside the QNode. -# +# ###################################################################### # **In order to fully support model saving, we need to import the following keras functions and mark # the layer as serializable**. More details about these functions can be `found # here `__. -# +# from keras.utils import register_keras_serializable from keras.saving import serialize_keras_object, deserialize_keras_object @@ -201,194 +206,205 @@ def serial_quantum_model(weights, x): ###################################################################### # Methods to implement for a custom layer # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# # To create a fully functional keras layer, the following methods **need to be implemented** -# +# # 1. ``__init__`` method: # ^^^^^^^^^^^^^^^^^^^^^^^ -# +# # The ``__init__`` method is used to accomplish the following: \* Create the instance variables that # define the QNode such as number of wires, circuit backend, etc. \* Create the instance variables # that define the circuit such as number of layers \* Select the pennylane interface based on keras # backend to be [“tf”,“torch” or “Jax”] \* Call the ``super().__init__(**kwargs)`` to pass generic # layer properties such as ‘*name*’ to the parent class ``__init__`` function. -# +# # 2. ``build()`` method: # ^^^^^^^^^^^^^^^^^^^^^^ -# +# # The ``build()`` method is used to instantiate the model weights at runtime based on input_shape. # This can be used to create dynamic circuits with qubits equal to the number of input variables. # However in this demo we are ignoring the input shape. Weights are created using the ``add_weight`` # method, which you can read more about # `here `__. -# +# # **Note: DO NOT** *apply any operations on the created weight here as it will cause issues with # gradients* -# +# # 3. ``compute_output_shape()`` method: # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# +# # This method is required in order to support model.summary() and model.save() functionality. This can # be trivially implemented by passing an output_shape parameter in the ``__init__`` method similar to # the depricated ``qml.KerasLayer``, or we can implement circuit specific logic. In this example, our # circuit outputs a single expectation value per input variable, therefore the output shape is # ‘**(batch,num_of_wires)**’ -# +# # 4. ``QNode`` methods: # ^^^^^^^^^^^^^^^^^^^^^ -# +# # The ‘QNode’ methods consist of 2 sets of methods - 1. **Circuit Definition Methods** : These methods # create the QNode circuit structure and can be implemented as a single method or a set of methods # which implement different sub-circuits. Here we use 2 sub-circuit methods ``self.W`` and ``self.S``, # along with the ``self.serial_quantum_model`` method to define the final structure and returned # measurements. 2. **Circuit Creation Method** : This method defines the pennylane device and the # QNode from the circuit definition as an instance variables. -# +# # **Note**\ *: Pennylane requires the input to be the last argument to properly work with batched # inputs* -# +# # 5. ``call()`` method: # ^^^^^^^^^^^^^^^^^^^^^ -# +# # The ``call()`` methods needs to call the Qnode with the weight variable. Additional pre-processing # can be applied before calling the circuit as well, for example we apply input variable scaling # outside the circuit to take advantage of efficient vectorized execution for batched input. Depending # on your specific model, we can also include pre-processing steps such as input scaling. Due to being # wrapped by the autodiff of the various backends, we will still get valid gradients for these steps. -# +# # 6. ``draw_qnode`` method: # ^^^^^^^^^^^^^^^^^^^^^^^^^ -# +# # A Utility methods to plot the QNode circuits -# +# # 7. ``get_config`` method: # ^^^^^^^^^^^^^^^^^^^^^^^^^ -# +# # This method needs to be implemented to support ``model.save`` functionality. It creates a # config(dict) which defines the configuration of the current layer. -# +# # **Note**: While scalar data such as ``int``, ``float`` and ``str``, do not need the # ``serialize_keras_object`` function, it is typically good practice to wrap all the parameters using # this method. -# +# # 8. ``from_config`` method: # ^^^^^^^^^^^^^^^^^^^^^^^^^^ -# +# # This method needs to be implemented to support ``model.save`` functionality. It defines how to # create an instance of the layer from a configuration. -# +# + @register_keras_serializable(package="QKeras", name="QKerasLayer") class QKerasLayer(keras.layers.Layer): - def __init__(self,layers:int, - scaling:float=1.0, - circ_backend="lightning.qubit", - circ_grad_method="adjoint", - num_wires:int=1, - **kwargs): + def __init__( + self, + layers: int, + scaling: float = 1.0, + circ_backend="lightning.qubit", + circ_grad_method="adjoint", + num_wires: int = 1, + **kwargs, + ): """A Keras Layer wrapping a pennylane Q-Node. Args: - layers (int): Number of layers in the DR Model. + layers (int): Number of layers in the DR Model. circ_backend (str): Backend for the quantum circuit. Defaults to 'lightning.qubit' circ_grad_method (str): Gradient method for the quantum circuit. Defaults to 'adjoint' num_wires (int): Number of wires to iniatize the qml.device. Defaults to 1. scaling (float): Scaling factor for the input data. Defaults to 1.0 **kwargs: Additional keyword arguments for the keras Layer class such as 'name'. """ - super().__init__(**kwargs) # Passing the keyword arguments to the parent class + super().__init__(**kwargs) # Passing the keyword arguments to the parent class # Defining the circuit parameters self.layers = layers self.scaling = scaling self.circ_backend = circ_backend self.circ_grad_method = circ_grad_method self.num_wires = num_wires - + # Define Keras Layer flags - self.is_built : bool = False - + self.is_built: bool = False + # Selecting the Pennylane interface based on keras backend - if(keras.config.backend() =="torch"): + if keras.config.backend() == "torch": self.interface = "torch" - elif(keras.config.backend() =="tensorflow"): + elif keras.config.backend() == "tensorflow": self.interface = "tf" - elif(keras.config.backend() =="jax"): + elif keras.config.backend() == "jax": self.interface = "jax" - - def build(self,input_shape): - """ Initialized the layer weights based on input_shape + + def build(self, input_shape): + """Initialized the layer weights based on input_shape Args:s - input_shape [tuple]: The shape of the input + input_shape [tuple]: The shape of the input """ - # Save input_shape without batch to be used later for the draw_circuit function + # Save input_shape without batch to be used later for the draw_circuit function self.input_shape = input_shape[1:] - + ## We initialize weights in the same way as the numpy array in the previous section. # Randomly initialize weights to uniform distribution in the a range of [0,2pi) - self.layer_weights = self.add_weight(shape=(self.layers+1,3),initializer = keras.initializers.random_uniform(minval=0,maxval=2*np.pi), - trainable=True) - + self.layer_weights = self.add_weight( + shape=(self.layers + 1, 3), + initializer=keras.initializers.random_uniform(minval=0, maxval=2 * np.pi), + trainable=True, + ) + # Create Quantum Circuit self.create_circuit() - + # Set the layer as built self.is_built = True - - def compute_output_shape(self,input_shape): - """ Return output shape as a function of the input shape""" + def compute_output_shape(self, input_shape): + """Return output shape as a function of the input shape""" # For this model we return an expectation value per qubit. The '0' index of the input_shape is always the batch, so we return an output shape of (batch, num_wires) - return (input_shape[0],self.num_wires) + return (input_shape[0], self.num_wires) ## We define the subcircuit functions for the circuit. - def S(self,x): + def S(self, x): """Data-encoding circuit block.""" - # Use the [:,0] syntax for batch support - qml.RX(x[:,0], wires=0) - - def W(self,theta): + # Use the [:,0] syntax for batch support + qml.RX(x[:, 0], wires=0) + + def W(self, theta): """Trainable circuit block.""" qml.Rot(theta[0], theta[1], theta[2], wires=0) ## Define the QNode code as a class method **without qml.qnode decorator** - def serial_quantum_model(self,weights, x): - """ Data Re-Uploading QML model""" + def serial_quantum_model(self, weights, x): + """Data Re-Uploading QML model""" for theta in weights[:-1]: self.W(theta) self.S(x) - + # (L+1)'th unitary self.W(weights[-1]) - + return qml.expval(qml.PauliZ(wires=0)) def create_circuit(self): - """ Creates the pennylane device and QNode""" - dev = qml.device(self.circ_backend, wires = self.num_wires) - self.circuit = qml.QNode(self.serial_quantum_model, dev, diff_method=self.circ_grad_method, interface=self.interface) - - def call(self,inputs): - """Defines the forward pass of the layer """ + """Creates the pennylane device and QNode""" + dev = qml.device(self.circ_backend, wires=self.num_wires) + self.circuit = qml.QNode( + self.serial_quantum_model, + dev, + diff_method=self.circ_grad_method, + interface=self.interface, + ) + + def call(self, inputs): + """Defines the forward pass of the layer""" ## We need to prevent the layer from being called before the weights and circuit are built - if (not self.is_built): + if not self.is_built: raise Exception("Layer not built") from None - - # We multiply the input with the scaling factor outside the circuit for optimized vector execution. + + # We multiply the input with the scaling factor outside the circuit for optimized vector execution. x = ops.multiply(self.scaling, inputs) # We call the circuit with the weight variables. out = self.circuit(self.layer_weights, x) return out def draw_qnode(self): - """ Draw the layer circuit""" + """Draw the layer circuit""" ## We want to raise an exception if this function is called before our QNode is created - if (not self.is_built): + if not self.is_built: raise Exception("Layer not built") from None ## Create a random input using the input_shape defined earlier with a single batch dim - x = ops.expand_dims(keras.random.uniform(shape = self.input_shape),0) + x = ops.expand_dims(keras.random.uniform(shape=self.input_shape), 0) qml.draw_mpl(self.circuit)(self.layer_weights, x) def get_config(self): - """ Create layer config for layer saving""" + """Create layer config for layer saving""" ## Load the basic config parameters of the keras.layer parent class base_config = super(QKerasLayer, self).get_config() ## Create a custom configuration for the instance variables unique to the QNode @@ -401,9 +417,9 @@ def get_config(self): } return {**base_config, **config} - @classmethod # Note that this needs to be a class function and not an instance method + @classmethod # Note that this needs to be a class function and not an instance method def from_config(cls, config): - """ Create an instance of layer from config""" + """Create an instance of layer from config""" # The cls argument is the specific layer config and the config object contains general keras.layer arguments layers = deserialize_keras_object(config.pop("layers")) scaling = deserialize_keras_object(config.pop("scaling")) @@ -411,68 +427,68 @@ def from_config(cls, config): circ_grad_method = deserialize_keras_object(config.pop("circ_grad_method")) num_wires = deserialize_keras_object(config.pop("num_wires")) # Call the init function of the layer from the config - return cls(layers=layers, - scaling= scaling, - circ_backend=circ_backend, - circ_grad_method=circ_grad_method, - num_wires = num_wires, - **config) - + return cls( + layers=layers, + scaling=scaling, + circ_backend=circ_backend, + circ_grad_method=circ_grad_method, + num_wires=num_wires, + **config, + ) + ###################################################################### # We can now test out our layer class by initializing it using the same arguments as the previous # section -# +# -layers = 2 +layers = 2 -keras_layer = QKerasLayer(layers = layers, - scaling=1.0, - circ_backend="lightning.qubit", - num_wires=1, - name="QuantumLayer") +keras_layer = QKerasLayer( + layers=layers, scaling=1.0, circ_backend="lightning.qubit", num_wires=1, name="QuantumLayer" +) ###################################################################### # Integrating the layer in a Keras Model # -------------------------------------- -# +# # In order to test the layer functionality, let’s integrate it into a simple keras model -# +# # Simple univariate input layer inp = keras.layers.Input(shape=(1,)) out = keras_layer(inp) -model = keras.models.Model(inputs=inp,outputs=out,name="QuantumModel") +model = keras.models.Model(inputs=inp, outputs=out, name="QuantumModel") ###################################################################### # Lets look at the model summary. We can verify if everything looks correct based on - \* The number # of trainable parameters - Since our weights are of the shape (layers+1,3), we can expect a shape of # (2+1,3) = (3,3) = 9 parameters \* The name of the layer matching what we passed in the instantiation -# +# model.summary() ###################################################################### # Plotting inner QNode # ~~~~~~~~~~~~~~~~~~~~ -# +# # Integrating the layer into a model and calling the ``model.summary()`` function also calls the # ``layer.build`` function. Therefore our circuit,weights and device should be instantiated. We can # verify this calling our ``draw_qnode`` helper function to see the circuit plot. -# +# keras_layer.draw_qnode() ###################################################################### # Test forward pass # ~~~~~~~~~~~~~~~~~ -# +# # Similar to earlier, lets test our layer inference by calling the model with the random weights and # plotting the outputs. -# +# # **Note:** *When using the torch backend, we might need to call the .to(‘cpu’) on the model # predictions before we can plot them if the system has a GPU* -# +# x = np.linspace(-6, 6, 70) random_quantum_model_y = model(x) @@ -486,31 +502,33 @@ def from_config(cls, config): ###################################################################### # Model Training # -------------- -# +# # We can now train the model using the normal keras training functions of ``model.compile`` and # ``model.fit``. -# +# # We will first compile the model with the ``mean_squared_error`` loss function and the ``Adam`` # optimizer -# +# -model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.03), - loss = keras.losses.mean_squared_error,run_eagerly=True) +model.compile( + optimizer=keras.optimizers.Adam(learning_rate=0.03), + loss=keras.losses.mean_squared_error, + run_eagerly=True, +) ###################################################################### # We can then train the model with the ``model.fit`` function for x and target_y. -# +# -model.fit(x=x,y=target_y, - epochs=30) +model.fit(x=x, y=target_y, epochs=30) ###################################################################### # Plotting the outputs of the trained model against the ground truth # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# # The model should train relatively fast. If your loss is :math:`<10^{-2}`, the fit should be very # good -# +# predictions = model(x) ## Uncomment the following line for the torch backend @@ -525,26 +543,26 @@ def from_config(cls, config): ###################################################################### # Model Saving and Loading # ------------------------ -# +# # Due to implementing the ``get_config`` and ``from_config`` methods, our model should be fully # compatible with the ``keras.save`` and ``keras.models.load_model`` methods. -# +# ###################################################################### # Saving # ~~~~~~ -# +# # Lets first save the trained models. It should save with no errors and create a ‘.keras’ file. -# +# model.save("./model.keras") ###################################################################### # Loading # ~~~~~~~ -# +# # Now lets test model loading and see if we can get the same inference with the loaded model -# +# model2 = keras.models.load_model("./model.keras") @@ -552,7 +570,7 @@ def from_config(cls, config): ###################################################################### # Now plotting the outputs we should see similiar if not identical results -# +# predictions2 = model2(x) ## Uncomment the following line for the torch backend @@ -567,7 +585,7 @@ def from_config(cls, config): ###################################################################### # Final Notes # =========== -# +# # Try changing the keras backend variable in the beginning of this demo and see how the process works # with a different backend -# \ No newline at end of file +# From 505ea88c56690357e1cdd3d39209d490502e004d Mon Sep 17 00:00:00 2001 From: vinayak19th Date: Mon, 22 Sep 2025 01:09:53 -0700 Subject: [PATCH 06/14] Finished meta data --- demonstrations_v2/tutorial_keras_3_training/metadata.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/demonstrations_v2/tutorial_keras_3_training/metadata.json b/demonstrations_v2/tutorial_keras_3_training/metadata.json index 8b3b93a9f7..003f31d6a6 100644 --- a/demonstrations_v2/tutorial_keras_3_training/metadata.json +++ b/demonstrations_v2/tutorial_keras_3_training/metadata.json @@ -9,7 +9,12 @@ "executable_latest": true, "dateOfPublication": "2025-09-22T00:00:00+00:00", "dateOfLastModification": "2025-09-22T00:00:00+00:00", - "categories": [], + "categories": [ + "Getting Started", + "Quantum Machine Learning", + "How-to", + "Devices and Performance" + ], "tags": [], "previewImages": [ { From fe054e2439a37b6507ce8f7ff115bd9065747edf Mon Sep 17 00:00:00 2001 From: vinayak19th Date: Mon, 22 Sep 2025 01:27:20 -0700 Subject: [PATCH 07/14] Finished demo --- .../tutorial_keras_3_training/demo.py | 35 ++++++++----------- .../tutorial_keras_3_training/requirements.in | 5 +-- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/demonstrations_v2/tutorial_keras_3_training/demo.py b/demonstrations_v2/tutorial_keras_3_training/demo.py index ba50286e58..07da0279bc 100644 --- a/demonstrations_v2/tutorial_keras_3_training/demo.py +++ b/demonstrations_v2/tutorial_keras_3_training/demo.py @@ -187,11 +187,11 @@ def serial_quantum_model(weights, x): # You can refer to the full tutorial on creating custom keras layer # `here `__. We will now create # a custom keras layer can wrap the quantum circuit and its trainable weights. When doing this we have -# to keep in mind the following Keras 3 specifics: 1. Do not use any ``tf.xx`` functions, and only use -# the native ``ops.xxx`` package. For example use ``ops.sum()`` rather than ``tf.reduce_sum`` or -# ``torch.sum``. 2. Do not create ``tf.constant`` or ``tf.variables``, rather use the -# ``self.add_weight`` method. 3. You need to pass the weights as arguments to the QNode and not use -# ``self.weight`` inside the QNode. +# to keep in mind the following Keras 3 specifics: +# +# 1. Do not use any ``tf.xx`` functions, and only use the native ``ops.xxx`` package. For example use ``ops.sum()`` rather than ``tf.reduce_sum`` or ``torch.sum``. +# 2. Do not create ``tf.constant`` or ``tf.variables``, rather use the``self.add_weight`` method. +# 3. You need to pass the weights as arguments to the QNode and not use ``self.weight`` inside the QNode. # ###################################################################### @@ -209,8 +209,7 @@ def serial_quantum_model(weights, x): # # To create a fully functional keras layer, the following methods **need to be implemented** # -# 1. ``__init__`` method: -# ^^^^^^^^^^^^^^^^^^^^^^^ +# **1.** ``__init__`` **method:** # # The ``__init__`` method is used to accomplish the following: \* Create the instance variables that # define the QNode such as number of wires, circuit backend, etc. \* Create the instance variables @@ -218,8 +217,7 @@ def serial_quantum_model(weights, x): # backend to be [“tf”,“torch” or “Jax”] \* Call the ``super().__init__(**kwargs)`` to pass generic # layer properties such as ‘*name*’ to the parent class ``__init__`` function. # -# 2. ``build()`` method: -# ^^^^^^^^^^^^^^^^^^^^^^ +# **2.** ``build()`` **method:** # # The ``build()`` method is used to instantiate the model weights at runtime based on input_shape. # This can be used to create dynamic circuits with qubits equal to the number of input variables. @@ -230,8 +228,7 @@ def serial_quantum_model(weights, x): # **Note: DO NOT** *apply any operations on the created weight here as it will cause issues with # gradients* # -# 3. ``compute_output_shape()`` method: -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# **3.** ``compute_output_shape()`` **method:** # # This method is required in order to support model.summary() and model.save() functionality. This can # be trivially implemented by passing an output_shape parameter in the ``__init__`` method similar to @@ -239,8 +236,8 @@ def serial_quantum_model(weights, x): # circuit outputs a single expectation value per input variable, therefore the output shape is # ‘**(batch,num_of_wires)**’ # -# 4. ``QNode`` methods: -# ^^^^^^^^^^^^^^^^^^^^^ +# **4.** ``QNode`` **methods:** +# # # The ‘QNode’ methods consist of 2 sets of methods - 1. **Circuit Definition Methods** : These methods # create the QNode circuit structure and can be implemented as a single method or a set of methods @@ -252,8 +249,7 @@ def serial_quantum_model(weights, x): # **Note**\ *: Pennylane requires the input to be the last argument to properly work with batched # inputs* # -# 5. ``call()`` method: -# ^^^^^^^^^^^^^^^^^^^^^ +# **5.** ``call()`` **method:** # # The ``call()`` methods needs to call the Qnode with the weight variable. Additional pre-processing # can be applied before calling the circuit as well, for example we apply input variable scaling @@ -261,13 +257,11 @@ def serial_quantum_model(weights, x): # on your specific model, we can also include pre-processing steps such as input scaling. Due to being # wrapped by the autodiff of the various backends, we will still get valid gradients for these steps. # -# 6. ``draw_qnode`` method: -# ^^^^^^^^^^^^^^^^^^^^^^^^^ +# **6.** ``draw_qnode`` **method:** # # A Utility methods to plot the QNode circuits # -# 7. ``get_config`` method: -# ^^^^^^^^^^^^^^^^^^^^^^^^^ +# **7.** ``get_config`` **method:** # # This method needs to be implemented to support ``model.save`` functionality. It creates a # config(dict) which defines the configuration of the current layer. @@ -276,8 +270,7 @@ def serial_quantum_model(weights, x): # ``serialize_keras_object`` function, it is typically good practice to wrap all the parameters using # this method. # -# 8. ``from_config`` method: -# ^^^^^^^^^^^^^^^^^^^^^^^^^^ +# **8.** ``from_config`` **method:** # # This method needs to be implemented to support ``model.save`` functionality. It defines how to # create an instance of the layer from a configuration. diff --git a/demonstrations_v2/tutorial_keras_3_training/requirements.in b/demonstrations_v2/tutorial_keras_3_training/requirements.in index 7c2fd511d2..aad78486ca 100644 --- a/demonstrations_v2/tutorial_keras_3_training/requirements.in +++ b/demonstrations_v2/tutorial_keras_3_training/requirements.in @@ -1,6 +1,3 @@ keras==3.11.3 -matplotlib==3.10.6 -numpy==2.3.3 pennylane==0.42.3 -tensorflow==2.20.0 -torch==2.8.0+cu126 +torch>2.0.0 From 2bba6e1e92f08669516102aaf73f91ca7f591db8 Mon Sep 17 00:00:00 2001 From: vinayak19th Date: Mon, 22 Sep 2025 01:35:51 -0700 Subject: [PATCH 08/14] Added tensorflow as requirement --- .gitignore | 1 + .../tutorial_keras_3_training/demo.py | 21 +++++++++++-------- .../tutorial_keras_3_training/requirements.in | 1 + 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 3399da9134..db874f23c6 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ sg_execution_times.rst # Generated after installation .github/workflows/qml_pipeline_utils/build/ +.vscode/ \ No newline at end of file diff --git a/demonstrations_v2/tutorial_keras_3_training/demo.py b/demonstrations_v2/tutorial_keras_3_training/demo.py index 07da0279bc..e1f901ea28 100644 --- a/demonstrations_v2/tutorial_keras_3_training/demo.py +++ b/demonstrations_v2/tutorial_keras_3_training/demo.py @@ -5,7 +5,7 @@ ###################################################################### # While pennylane does not support the ``qml.KerasLayer`` api since the transition from Keras 2 to # Keras 3, we can still define a custom keras layer with certain modifications to allow for -# integration into keras models. Additionally, due to the multibackend support in Keras 3, these +# integration into keras models. Additionally, due to the multi-backend support in Keras 3, these # models can we trained using jax, pytorch or tensorflow. This demo will create a Keras 3 # implementation of the ``Data-ReUploading`` models from the `‘Quantum models as Fourier # series’ `__ demo. @@ -30,7 +30,7 @@ ###################################################################### # We can now import keras alongside its key modules ``ops``. We then print the current backend to -# verify if everthing loaded correctly. +# verify if everything loaded correctly. # import keras @@ -39,7 +39,7 @@ print(f"Keras backend: {keras.backend.backend()}") ###################################################################### -# \**In order to ensure numerical stability with quantum circuits set the backend to use ``float64`` +# **In order to ensure numerical stability with quantum circuits set the backend to use ``float64``** # keras.backend.set_floatx("float64") @@ -61,7 +61,7 @@ # The main goal of this demo is to allow people to integrate pennylane circuits into their existing # code bases. The Keras Layer created here will fully support models saving/loading, training and # everything else you normally expect from a Keras Layer. Additionally, they will be entirely self -# contained, not requiring Qnodes to be defined externally. +# contained, not requiring QNodes to be defined externally. # # In order to get better background on the concepts employed in this demo, here are some helpful # additional resources: \* `Keras custom layer @@ -75,7 +75,7 @@ # Setting up the target dataset # ----------------------------- # -# Similiar to the [fourier series +# Similar to the [fourier series # demo]((https://pennylane.ai/qml/demos/tutorial_expressivity_fourier_series) mentioned in the # introduction, we first define a (classical) target function which will be used as a “ground truth” # that the quantum model has to fit. The target function is constructed as a Fourier series of a @@ -232,7 +232,7 @@ def serial_quantum_model(weights, x): # # This method is required in order to support model.summary() and model.save() functionality. This can # be trivially implemented by passing an output_shape parameter in the ``__init__`` method similar to -# the depricated ``qml.KerasLayer``, or we can implement circuit specific logic. In this example, our +# the deprecated ``qml.KerasLayer``, or we can implement circuit specific logic. In this example, our # circuit outputs a single expectation value per input variable, therefore the output shape is # ‘**(batch,num_of_wires)**’ # @@ -251,7 +251,7 @@ def serial_quantum_model(weights, x): # # **5.** ``call()`` **method:** # -# The ``call()`` methods needs to call the Qnode with the weight variable. Additional pre-processing +# The ``call()`` methods needs to call the QNode with the weight variable. Additional pre-processing # can be applied before calling the circuit as well, for example we apply input variable scaling # outside the circuit to take advantage of efficient vectorized execution for batched input. Depending # on your specific model, we can also include pre-processing steps such as input scaling. Due to being @@ -293,7 +293,7 @@ def __init__( layers (int): Number of layers in the DR Model. circ_backend (str): Backend for the quantum circuit. Defaults to 'lightning.qubit' circ_grad_method (str): Gradient method for the quantum circuit. Defaults to 'adjoint' - num_wires (int): Number of wires to iniatize the qml.device. Defaults to 1. + num_wires (int): Number of wires to initialize the qml.device. Defaults to 1. scaling (float): Scaling factor for the input data. Defaults to 1.0 **kwargs: Additional keyword arguments for the keras Layer class such as 'name'. """ @@ -343,7 +343,7 @@ def compute_output_shape(self, input_shape): # For this model we return an expectation value per qubit. The '0' index of the input_shape is always the batch, so we return an output shape of (batch, num_wires) return (input_shape[0], self.num_wires) - ## We define the subcircuit functions for the circuit. + ## We define the sub-circuit functions for the circuit. def S(self, x): """Data-encoding circuit block.""" # Use the [:,0] syntax for batch support @@ -575,6 +575,9 @@ def from_config(cls, config): plt.ylim(-1, 1) plt.show() +###################################################################### +# image:: Icon-pictures.png + ###################################################################### # Final Notes # =========== diff --git a/demonstrations_v2/tutorial_keras_3_training/requirements.in b/demonstrations_v2/tutorial_keras_3_training/requirements.in index aad78486ca..46b60f1eb5 100644 --- a/demonstrations_v2/tutorial_keras_3_training/requirements.in +++ b/demonstrations_v2/tutorial_keras_3_training/requirements.in @@ -1,3 +1,4 @@ keras==3.11.3 pennylane==0.42.3 torch>2.0.0 +tensorflow>2.16.0 \ No newline at end of file From 71222a2ad7d53d6ba303a3bcd89042d08fd7d356 Mon Sep 17 00:00:00 2001 From: vinayak19th Date: Mon, 22 Sep 2025 01:36:33 -0700 Subject: [PATCH 09/14] removed local vscode settings --- .gitignore | 3 ++- .vscode/settings.json | 7 ------- 2 files changed, 2 insertions(+), 8 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index db874f23c6..c0c450652a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ sg_execution_times.rst # Generated after installation .github/workflows/qml_pipeline_utils/build/ -.vscode/ \ No newline at end of file +.vscode/ +.vscode/settings.json \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 388c155d48..0000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "python.analysis.ignore": [ - ".venv/**", - ".venv-build/**", - "_build/**" - ] -} From bab6e7cbd237289a8c30b69fc5cd9b785cac9332 Mon Sep 17 00:00:00 2001 From: vinayak19th Date: Mon, 22 Sep 2025 01:38:27 -0700 Subject: [PATCH 10/14] Added preview --- demonstrations_v2/tutorial_keras_3_training/metadata.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/demonstrations_v2/tutorial_keras_3_training/metadata.json b/demonstrations_v2/tutorial_keras_3_training/metadata.json index 003f31d6a6..e8c6134d1a 100644 --- a/demonstrations_v2/tutorial_keras_3_training/metadata.json +++ b/demonstrations_v2/tutorial_keras_3_training/metadata.json @@ -19,7 +19,7 @@ "previewImages": [ { "type": "thumbnail", - "uri": "/_static/demo_thumbnails/regular_demo_thumbnails/thumbnail_placeholder.png" + "uri": "/_static/demonstration_assets/qnn_module/Keras_logo.png" } ], "seoDescription": "A demo showing how to integrate a pennylane circuit into keras 3 and train it using both pytorch and tensorflow", @@ -28,4 +28,5 @@ "basedOnPapers": [], "referencedByPapers": [], "relatedContent": [] -} \ No newline at end of file +} + From 63bfd7c7bcf1ae856776ae2a4b695ada0de0afef Mon Sep 17 00:00:00 2001 From: vinayak19th Date: Mon, 22 Sep 2025 01:39:50 -0700 Subject: [PATCH 11/14] Correctly renamed asset files --- .../demo_0aec1f9e-ee6b-4237-8372-32396d5dd85a_1.png | Bin .../demo_20c945e3-08f4-4e92-a98e-4dd6392ae14e_1.png | Bin .../demo_234a1746-683d-4d8b-8674-4681a3b2cff5_1.png | Bin .../demo_23cde79d-79d0-40da-b8f7-a80cfd8d8fc1_1.png | Bin .../demo_78759557-918c-4b68-b975-65e4e9f2d0f4_1.png | Bin .../demo_d716da26-80d9-41f4-ab6a-567bba016fd6_1.png | Bin .../demo_d9dbeff1-fdad-423e-80ef-f7e8439a20f0_1.png | Bin 7 files changed, 0 insertions(+), 0 deletions(-) rename _static/demonstration_assets/{demo => keras_3_training}/demo_0aec1f9e-ee6b-4237-8372-32396d5dd85a_1.png (100%) rename _static/demonstration_assets/{demo => keras_3_training}/demo_20c945e3-08f4-4e92-a98e-4dd6392ae14e_1.png (100%) rename _static/demonstration_assets/{demo => keras_3_training}/demo_234a1746-683d-4d8b-8674-4681a3b2cff5_1.png (100%) rename _static/demonstration_assets/{demo => keras_3_training}/demo_23cde79d-79d0-40da-b8f7-a80cfd8d8fc1_1.png (100%) rename _static/demonstration_assets/{demo => keras_3_training}/demo_78759557-918c-4b68-b975-65e4e9f2d0f4_1.png (100%) rename _static/demonstration_assets/{demo => keras_3_training}/demo_d716da26-80d9-41f4-ab6a-567bba016fd6_1.png (100%) rename _static/demonstration_assets/{demo => keras_3_training}/demo_d9dbeff1-fdad-423e-80ef-f7e8439a20f0_1.png (100%) diff --git a/_static/demonstration_assets/demo/demo_0aec1f9e-ee6b-4237-8372-32396d5dd85a_1.png b/_static/demonstration_assets/keras_3_training/demo_0aec1f9e-ee6b-4237-8372-32396d5dd85a_1.png similarity index 100% rename from _static/demonstration_assets/demo/demo_0aec1f9e-ee6b-4237-8372-32396d5dd85a_1.png rename to _static/demonstration_assets/keras_3_training/demo_0aec1f9e-ee6b-4237-8372-32396d5dd85a_1.png diff --git a/_static/demonstration_assets/demo/demo_20c945e3-08f4-4e92-a98e-4dd6392ae14e_1.png b/_static/demonstration_assets/keras_3_training/demo_20c945e3-08f4-4e92-a98e-4dd6392ae14e_1.png similarity index 100% rename from _static/demonstration_assets/demo/demo_20c945e3-08f4-4e92-a98e-4dd6392ae14e_1.png rename to _static/demonstration_assets/keras_3_training/demo_20c945e3-08f4-4e92-a98e-4dd6392ae14e_1.png diff --git a/_static/demonstration_assets/demo/demo_234a1746-683d-4d8b-8674-4681a3b2cff5_1.png b/_static/demonstration_assets/keras_3_training/demo_234a1746-683d-4d8b-8674-4681a3b2cff5_1.png similarity index 100% rename from _static/demonstration_assets/demo/demo_234a1746-683d-4d8b-8674-4681a3b2cff5_1.png rename to _static/demonstration_assets/keras_3_training/demo_234a1746-683d-4d8b-8674-4681a3b2cff5_1.png diff --git a/_static/demonstration_assets/demo/demo_23cde79d-79d0-40da-b8f7-a80cfd8d8fc1_1.png b/_static/demonstration_assets/keras_3_training/demo_23cde79d-79d0-40da-b8f7-a80cfd8d8fc1_1.png similarity index 100% rename from _static/demonstration_assets/demo/demo_23cde79d-79d0-40da-b8f7-a80cfd8d8fc1_1.png rename to _static/demonstration_assets/keras_3_training/demo_23cde79d-79d0-40da-b8f7-a80cfd8d8fc1_1.png diff --git a/_static/demonstration_assets/demo/demo_78759557-918c-4b68-b975-65e4e9f2d0f4_1.png b/_static/demonstration_assets/keras_3_training/demo_78759557-918c-4b68-b975-65e4e9f2d0f4_1.png similarity index 100% rename from _static/demonstration_assets/demo/demo_78759557-918c-4b68-b975-65e4e9f2d0f4_1.png rename to _static/demonstration_assets/keras_3_training/demo_78759557-918c-4b68-b975-65e4e9f2d0f4_1.png diff --git a/_static/demonstration_assets/demo/demo_d716da26-80d9-41f4-ab6a-567bba016fd6_1.png b/_static/demonstration_assets/keras_3_training/demo_d716da26-80d9-41f4-ab6a-567bba016fd6_1.png similarity index 100% rename from _static/demonstration_assets/demo/demo_d716da26-80d9-41f4-ab6a-567bba016fd6_1.png rename to _static/demonstration_assets/keras_3_training/demo_d716da26-80d9-41f4-ab6a-567bba016fd6_1.png diff --git a/_static/demonstration_assets/demo/demo_d9dbeff1-fdad-423e-80ef-f7e8439a20f0_1.png b/_static/demonstration_assets/keras_3_training/demo_d9dbeff1-fdad-423e-80ef-f7e8439a20f0_1.png similarity index 100% rename from _static/demonstration_assets/demo/demo_d9dbeff1-fdad-423e-80ef-f7e8439a20f0_1.png rename to _static/demonstration_assets/keras_3_training/demo_d9dbeff1-fdad-423e-80ef-f7e8439a20f0_1.png From 1949377f3dfaea1be784a1f86d9abe20243bac9d Mon Sep 17 00:00:00 2001 From: vinayak19th Date: Mon, 22 Sep 2025 01:49:22 -0700 Subject: [PATCH 12/14] Properly referenced and named images --- ...8e-4dd6392ae14e_1.png => circuit_plot.png} | Bin ...4e9f2d0f4_1.png => circuit_plot_keras.png} | Bin ...681a3b2cff5_1.png => loaded_model_fit.png} | Bin ...f-f7e8439a20f0_1.png => original_data.png} | Bin ...0cfd8d8fc1_1.png => random_init_keras.png} | Bin ...396d5dd85a_1.png => random_init_numpy.png} | Bin ...016fd6_1.png => trained_model_fit.png.png} | Bin .../tutorial_keras_3_training/demo.py | 52 +++++++++++++++++- 8 files changed, 51 insertions(+), 1 deletion(-) rename _static/demonstration_assets/keras_3_training/{demo_20c945e3-08f4-4e92-a98e-4dd6392ae14e_1.png => circuit_plot.png} (100%) rename _static/demonstration_assets/keras_3_training/{demo_78759557-918c-4b68-b975-65e4e9f2d0f4_1.png => circuit_plot_keras.png} (100%) rename _static/demonstration_assets/keras_3_training/{demo_234a1746-683d-4d8b-8674-4681a3b2cff5_1.png => loaded_model_fit.png} (100%) rename _static/demonstration_assets/keras_3_training/{demo_d9dbeff1-fdad-423e-80ef-f7e8439a20f0_1.png => original_data.png} (100%) rename _static/demonstration_assets/keras_3_training/{demo_23cde79d-79d0-40da-b8f7-a80cfd8d8fc1_1.png => random_init_keras.png} (100%) rename _static/demonstration_assets/keras_3_training/{demo_0aec1f9e-ee6b-4237-8372-32396d5dd85a_1.png => random_init_numpy.png} (100%) rename _static/demonstration_assets/keras_3_training/{demo_d716da26-80d9-41f4-ab6a-567bba016fd6_1.png => trained_model_fit.png.png} (100%) diff --git a/_static/demonstration_assets/keras_3_training/demo_20c945e3-08f4-4e92-a98e-4dd6392ae14e_1.png b/_static/demonstration_assets/keras_3_training/circuit_plot.png similarity index 100% rename from _static/demonstration_assets/keras_3_training/demo_20c945e3-08f4-4e92-a98e-4dd6392ae14e_1.png rename to _static/demonstration_assets/keras_3_training/circuit_plot.png diff --git a/_static/demonstration_assets/keras_3_training/demo_78759557-918c-4b68-b975-65e4e9f2d0f4_1.png b/_static/demonstration_assets/keras_3_training/circuit_plot_keras.png similarity index 100% rename from _static/demonstration_assets/keras_3_training/demo_78759557-918c-4b68-b975-65e4e9f2d0f4_1.png rename to _static/demonstration_assets/keras_3_training/circuit_plot_keras.png diff --git a/_static/demonstration_assets/keras_3_training/demo_234a1746-683d-4d8b-8674-4681a3b2cff5_1.png b/_static/demonstration_assets/keras_3_training/loaded_model_fit.png similarity index 100% rename from _static/demonstration_assets/keras_3_training/demo_234a1746-683d-4d8b-8674-4681a3b2cff5_1.png rename to _static/demonstration_assets/keras_3_training/loaded_model_fit.png diff --git a/_static/demonstration_assets/keras_3_training/demo_d9dbeff1-fdad-423e-80ef-f7e8439a20f0_1.png b/_static/demonstration_assets/keras_3_training/original_data.png similarity index 100% rename from _static/demonstration_assets/keras_3_training/demo_d9dbeff1-fdad-423e-80ef-f7e8439a20f0_1.png rename to _static/demonstration_assets/keras_3_training/original_data.png diff --git a/_static/demonstration_assets/keras_3_training/demo_23cde79d-79d0-40da-b8f7-a80cfd8d8fc1_1.png b/_static/demonstration_assets/keras_3_training/random_init_keras.png similarity index 100% rename from _static/demonstration_assets/keras_3_training/demo_23cde79d-79d0-40da-b8f7-a80cfd8d8fc1_1.png rename to _static/demonstration_assets/keras_3_training/random_init_keras.png diff --git a/_static/demonstration_assets/keras_3_training/demo_0aec1f9e-ee6b-4237-8372-32396d5dd85a_1.png b/_static/demonstration_assets/keras_3_training/random_init_numpy.png similarity index 100% rename from _static/demonstration_assets/keras_3_training/demo_0aec1f9e-ee6b-4237-8372-32396d5dd85a_1.png rename to _static/demonstration_assets/keras_3_training/random_init_numpy.png diff --git a/_static/demonstration_assets/keras_3_training/demo_d716da26-80d9-41f4-ab6a-567bba016fd6_1.png b/_static/demonstration_assets/keras_3_training/trained_model_fit.png.png similarity index 100% rename from _static/demonstration_assets/keras_3_training/demo_d716da26-80d9-41f4-ab6a-567bba016fd6_1.png rename to _static/demonstration_assets/keras_3_training/trained_model_fit.png.png diff --git a/demonstrations_v2/tutorial_keras_3_training/demo.py b/demonstrations_v2/tutorial_keras_3_training/demo.py index e1f901ea28..cebaed6d1c 100644 --- a/demonstrations_v2/tutorial_keras_3_training/demo.py +++ b/demonstrations_v2/tutorial_keras_3_training/demo.py @@ -110,6 +110,13 @@ def target_function(x): plt.ylim(-1, 1) plt.show() +###################################################################### +# .. figure:: ../_static/demonstration_assets/keras_3_training/original_data.png +# :width: 50% +# :align: center +# +# | + ###################################################################### # Defining the Quantum Model # -------------------------- @@ -169,6 +176,13 @@ def serial_quantum_model(weights, x): qml.draw_mpl(serial_quantum_model)(weights, 1) +###################################################################### +# .. figure:: ../_static/demonstration_assets/keras_3_training/circuit_plot.png +# :width: 50% +# :align: center +# +# | + ###################################################################### # Plotting the output of this random circuit, we get # @@ -180,6 +194,13 @@ def serial_quantum_model(weights, x): plt.ylim(-1, 1) plt.show() +###################################################################### +# .. figure:: ../_static/demonstration_assets/keras_3_training/random_init_numpy.png +# :width: 50% +# :align: center +# +# | + ###################################################################### # Wrapping the QNode in a Keras Layer # =================================== @@ -472,6 +493,14 @@ def from_config(cls, config): keras_layer.draw_qnode() +###################################################################### +# .. figure:: ../_static/demonstration_assets/keras_3_training/circuit_plot_keras.png +# :width: 50% +# :align: center +# +# | +# + ###################################################################### # Test forward pass # ~~~~~~~~~~~~~~~~~ @@ -492,6 +521,14 @@ def from_config(cls, config): plt.ylim(-1, 1) plt.show() +###################################################################### +# .. figure:: ../_static/demonstration_assets/keras_3_training/random_init_keras.png +# :width: 50% +# :align: center +# +# | +# + ###################################################################### # Model Training # -------------- @@ -533,6 +570,14 @@ def from_config(cls, config): plt.ylim(-1, 1) plt.show() +###################################################################### +# .. figure:: ../_static/demonstration_assets/keras_3_training/trained_model_fit.png +# :width: 50% +# :align: center +# +# | +# + ###################################################################### # Model Saving and Loading # ------------------------ @@ -576,7 +621,12 @@ def from_config(cls, config): plt.show() ###################################################################### -# image:: Icon-pictures.png +# .. figure:: ../_static/demonstration_assets/keras_3_training/loaded_model_fit.png +# :width: 50% +# :align: center +# +# | +# ###################################################################### # Final Notes From 6a0c059d98cdc9ad760c611ba97abf42ded97c0c Mon Sep 17 00:00:00 2001 From: Vinayak Sharma Date: Mon, 22 Sep 2025 09:39:38 -0700 Subject: [PATCH 13/14] Updated timezone information --- demonstrations_v2/tutorial_keras_3_training/metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demonstrations_v2/tutorial_keras_3_training/metadata.json b/demonstrations_v2/tutorial_keras_3_training/metadata.json index e8c6134d1a..cf058c5bb0 100644 --- a/demonstrations_v2/tutorial_keras_3_training/metadata.json +++ b/demonstrations_v2/tutorial_keras_3_training/metadata.json @@ -7,8 +7,8 @@ ], "executable_stable": true, "executable_latest": true, - "dateOfPublication": "2025-09-22T00:00:00+00:00", - "dateOfLastModification": "2025-09-22T00:00:00+00:00", + "dateOfPublication": "2025-09-21T11:10:00-07:00", + "dateOfLastModification": "2025-09-21T11:10:00-07:00", "categories": [ "Getting Started", "Quantum Machine Learning", From 30f65723d50cc5dcc9ff878841aca77510ebb0dc Mon Sep 17 00:00:00 2001 From: Vinayak Sharma Date: Mon, 22 Sep 2025 09:50:34 -0700 Subject: [PATCH 14/14] Fixed bullet point rendering issue for additional resources --- demonstrations_v2/tutorial_keras_3_training/demo.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/demonstrations_v2/tutorial_keras_3_training/demo.py b/demonstrations_v2/tutorial_keras_3_training/demo.py index cebaed6d1c..b153da5c23 100644 --- a/demonstrations_v2/tutorial_keras_3_training/demo.py +++ b/demonstrations_v2/tutorial_keras_3_training/demo.py @@ -64,11 +64,11 @@ # contained, not requiring QNodes to be defined externally. # # In order to get better background on the concepts employed in this demo, here are some helpful -# additional resources: \* `Keras custom layer -# documentation `__ \* -# `Pennylane QNode -# documentation `__ \* `Keras 3 -# Pytorch Example `__ +# additional resources: +# * `Keras custom layer +# documentation `__ +# *`Pennylane QNode documentation `__ +#* `Keras 3 Pytorch Example `__ # ######################################################################