From 954051f9d40e76edbbfabe3f298bbf514012fb28 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Tue, 17 Feb 2026 13:34:53 +0000 Subject: [PATCH 01/36] cleaned up imports, regenerated requirements, removed compiled files from git --- .gitignore | Bin 0 -> 44 bytes BOplayground.py | 29 +- EAplayground.py | 5 +- RSplayground.py | 2 + __pycache__/dataset.cpython-310.pyc | Bin 5260 -> 0 bytes .../chaincomplexembedding.cpython-310.pyc | Bin 26680 -> 0 bytes .../__pycache__/encoder.cpython-310.pyc | Bin 15258 -> 0 bytes .../__pycache__/gadget.cpython-310.pyc | Bin 15490 -> 0 bytes bayesian_optimization/bo.py | 1 - .../chaincomplexembedding.py | 2 - bayesian_optimization/encoder.py | 8 +- bayesian_optimization/gadget.py | 1 + bayesian_optimization/gp.py | 5 +- bayesian_optimization/graphembedding.py | 22 +- bayesian_optimization/kernels.py | 5 - bayesian_optimization/objective_function.py | 3 +- .../code_construction.cpython-310.pyc | Bin 18015 -> 0 bytes code_construction/code_construction.py | 3 +- dataset.py | 16 +- .../circuit_level_noise.cpython-310.pyc | Bin 13510 -> 0 bytes evaluation/__pycache__/cln.cpython-310.pyc | Bin 2030 -> 0 bytes .../css_decode_sim.cpython-310.pyc | Bin 11658 -> 0 bytes .../decoder_based_evaluation.cpython-310.pyc | Bin 12503 -> 0 bytes .../__pycache__/distance.cpython-310.pyc | Bin 1253 -> 0 bytes ...n_decoder_based_evaluation.cpython-310.pyc | Bin 2864 -> 0 bytes evaluation/__pycache__/qldpc.cpython-310.pyc | Bin 1570 -> 0 bytes evaluation/circuit_level_noise.py | 4 +- evaluation/decoder_based_evaluation.py | 7 +- .../__pycache__/ea.cpython-310.pyc | Bin 2272 -> 0 bytes evolutionary_algorithm/ea.py | 7 +- initial_points.py | 20 +- readme.ipynb | 251 +--- readme2.ipynb | 540 +-------- requirements.txt | 203 +--- test_bo.ipynb | 1017 +---------------- test_gp.ipynb | 668 +---------- 36 files changed, 193 insertions(+), 2626 deletions(-) create mode 100644 .gitignore delete mode 100644 __pycache__/dataset.cpython-310.pyc delete mode 100644 bayesian_optimization/__pycache__/chaincomplexembedding.cpython-310.pyc delete mode 100644 bayesian_optimization/__pycache__/encoder.cpython-310.pyc delete mode 100644 bayesian_optimization/__pycache__/gadget.cpython-310.pyc delete mode 100644 code_construction/__pycache__/code_construction.cpython-310.pyc delete mode 100644 evaluation/__pycache__/circuit_level_noise.cpython-310.pyc delete mode 100644 evaluation/__pycache__/cln.cpython-310.pyc delete mode 100644 evaluation/__pycache__/css_decode_sim.cpython-310.pyc delete mode 100644 evaluation/__pycache__/decoder_based_evaluation.cpython-310.pyc delete mode 100644 evaluation/__pycache__/distance.cpython-310.pyc delete mode 100644 evaluation/__pycache__/non_decoder_based_evaluation.cpython-310.pyc delete mode 100644 evaluation/__pycache__/qldpc.cpython-310.pyc delete mode 100644 evolutionary_algorithm/__pycache__/ea.cpython-310.pyc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..12829c01a33a63f07077d85ce9d9be3692ae3f3b GIT binary patch literal 44 pcmezWFPcf&6#|eFk0zE(R?IJ*XUr2LSfG2v`6B literal 0 HcmV?d00001 diff --git a/BOplayground.py b/BOplayground.py index de917a4..cd9ea84 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -5,28 +5,22 @@ import random import numpy as np import torch +from torch.nn.utils import clip_grad_norm_ +from torch.distributions.normal import Normal + from code_construction.code_construction import CodeConstructor from bayesian_optimization.objective_function import ObjectiveFunction from bayesian_optimization.encoder import * from bayesian_optimization.chaincomplexembedding import * from bayesian_optimization.gp import * +from bayesian_optimization.bo import BO_on_QEC + import gpytorch from gpytorch.mlls import ExactMarginalLogLikelihood -from gpytorch.distributions import MultivariateNormal from gpytorch.kernels import RBFKernel, MaternKernel, SpectralMixtureKernel, ScaleKernel import copy -from typing import Dict, Optional, Tuple, Any, List - -import torch -import gpytorch -from gpytorch.mlls import ExactMarginalLogLikelihood -from torch.nn.utils import clip_grad_norm_ -import math +from typing import Dict, Optional, Tuple, Any, List, Union from dataclasses import dataclass, asdict -from typing import Tuple, Union, Dict, Any - -from torch.distributions.normal import Normal -from bayesian_optimization.bo import BO_on_QEC class HillClimbing: @@ -896,9 +890,16 @@ def get_new_bb_vector(self,number): # y_init.append(y) # pl_init.append(pl) if l ==6 and g==3: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}_63.pkl" + if lambda_ == 1: + init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_1.0_63.pkl" + else: + init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}_63.pkl" + else: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" + if lambda_ == 1: + init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_1.0.pkl" + else: + init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" # file with 63 suffix has (l,m)=(6,3). Otherwise (l,m)=(12,6) with open(init_data_file, "rb") as f: data = pickle.load(f) diff --git a/EAplayground.py b/EAplayground.py index c67b1f6..cb74258 100644 --- a/EAplayground.py +++ b/EAplayground.py @@ -1,11 +1,8 @@ # Evolutionary algorim from code_construction.code_construction import CodeConstructor -import numpy as np from evolutionary_algorithm.ea import BivariateBicycleCodeEvolutionaryOptimization -from pymoo.operators.crossover.sbx import SBX from pymoo.operators.mutation.pm import PM from pymoo.operators.repair.rounding import RoundingRepair -from pymoo.operators.sampling.rnd import IntegerRandomSampling from pymoo.algorithms.soo.nonconvex.ga import GA from pymoo.operators.crossover.ux import UniformCrossover from pymoo.optimize import minimize @@ -18,6 +15,8 @@ # print(undetectable_error_rate.evaluate(p=0.01)) from pymoo.core.sampling import Sampling import sys + + if len(sys.argv) == 3: seed= int(sys.argv[1]) dataset_index = int(sys.argv[2]) diff --git a/RSplayground.py b/RSplayground.py index ea8f44c..0aada6e 100644 --- a/RSplayground.py +++ b/RSplayground.py @@ -4,6 +4,8 @@ from code_construction.code_construction import CodeConstructor from bayesian_optimization.objective_function import ObjectiveFunction import sys + + def set_all_seeds(seed: int = 42): random.seed(seed) np.random.seed(seed) diff --git a/__pycache__/dataset.cpython-310.pyc b/__pycache__/dataset.cpython-310.pyc deleted file mode 100644 index bc12cb7b1653a9699c091a06046fff2b3358f509..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5260 zcma)ATW=)A74E9OOi$1F?!`7C#4HJ62#f;(1tA0*ycb9oFR4*&&ZQo|p##6_4 z^t0l-c$#Uo?fIT=v(j3-?$<}(&9rCzIbF8X`SyapFsd*6@pRE&M1Ln;YA^fCr`pc= zXD%6{BHUYsa1Xfu2;Qo~!UH22(SeSFZ*aZiHz+%)1g5bDLce zZ+1IbF8fhgRXHE#VHW3Tef8~bCyzHnnRYK^dD0GPq$}H(yE{n~rWd5_%J;%N{&7dd zax;-pKglo0H{x`wn`CiBtD8^WCmcbG!S!SoT60q9T@=bTOiuk8lVLD z?iP`L$B9%%HmBA0A_|4E2dlfrWxX&CS;3CjkQYF)uIH#Wl{@CJg1*+FT~q)cw{Z6y^yl)W>+^+m zSfN@5)d~A$V+e@R*x7Q<4&A~!Vo;sCr43!NRI!~>i%(*eat5NoVO`0fbOnuSXl(f_7K*Q!3Yk;Ceqy%NF@{OeoKiAehK?5)=IB5}A;!K@cW0B4XEg zoVl)BSvGZjgsKyXP;w4AdlTXm6#xYZ6VA4+$&pAv(;`SBRi`7Ws%99uBruZ#Bt27g~4xkOB0+@EP`f45P(zKE=FoM_P%WDVT`3gp-Y`GHu!w~S%6s7_qJCWA_0iVEQQ0zm81c%TJ9SGad{oS}VR zbyQEa7DWiyMs*9udW;MCI97uS^7^VuOb`h0I-J{t2MA{o`GRpWQ6L>(Mb4gr$jOnA zive4aZNS>t4mS2D2J*#LMh@5B+WF%jDBXP>6KLZ&7RlkS7^u@<>h11l#Q#A|0YD%T z0r>9C{Xx)5!c5t@43iY@n$)TI?oH%@f0l5WmT|*Wzd#2VfsQc1%9bX*YCH~5eBQe} z1vc%f7rL!Im{D>g9<%)Gw8>cra?TuVh5T=8Za{=L2E#E+HD67;SbO$b^MP?LBI2=n zVZPh&N?x9$QZyhdVTdD`g6IH@rsEO_QDUt}(G79H;3xym2mgXRCG zgcDdH+)$=PWE36&B>>J{hJaBo>MkqSdvb1734%241c6Weh&dcKSUNeIZjWfpaUxes zc4s1I2&s)<8{3Rxz+3Ln0Zmkp$>Pida^)sjm8OU{ZzF0DHr&2fIE6>dct8%`l;`mU zIs9r-LHwXwhz9VzIer_~3KwlPs>j$4th2gme1_<|sC{O9Vd=T_JfMpaz1&6sr_1gU zL%C8|@{`_0TISg}cp*`VnWwPhrs(eg`IYqwB94 z&l_3sF;+sq50J<8joQ`&=R#^C*OgIBb{pq{(8+)j=!_6N$xEn)p`OD-euu=fRKXOx zDao^zaM5OC6Ckhs0+kuXWfay8o}NCf6M&sqiS9Nyb16oY4C?CatdB#+W^+QJ1Dj0c z!@-%4o*fzax^@ohInO2^Bmd$dHtuptYbq}rlEL&vWS1zCb(&dA-hbr*I9fIN_ z0=>2{i2^{#H0Uf1Cj`O(5-$i&Cc$ZF=d7@W1yIcmoty*8&Jp`LA6AC$G^9E%!1VMf zFyR&gg2F8Xv)D9?CKYGPuaFbM3Chb@f_xi7aoHLyW&J3MvuwMcru*ykO)MIYyh0zn zM}kO1(%L7Q9Y;A67p4z_&OlYSaof;wh5X5hPN$vM1+@MWIiuLsWsf43nP-oEMb4|v z2dmS%opzrOMAKv>w7h?i6C~$VAn3#sG`EJRd1NMc`DFPhlLuC2B5s~qm8t~+FrVac zI|y#$BkkIj`Cw&wJyYM1m;64yYq;_SJmmLCyhuVvPH$0(FeYicl8`nv<0O1~2@N!i zzHPV$0C(0iy(j9Pq!yU8bG6^XtvtfpiA1}nl#%I=L#c^ZMfe1Q*LP?gr}*}-;g5U= zaYjW|v8U0n<6&qhDwC6-XZBr)+&Ivewt|yWL-YW|l8OUd*boGjWjs-15!y<>WGg%C z;~H8j;@B!xCBd}>h#TF{{CW$2H;lO7QsSpg>PdlK84MlZN>DD8D-%pr!b3ZTlT0V+41Y(RX%tMjFg;<(anv$S zR3_EYeDVVln}j*H#8IJ&jks%&b%q9w(+QP^8Frp2U@{^6hw57d+~*8=`z>ZRR0O7b)_Iq6jQag1Rg@I!#gsWm6C>Qj!CjUN81Nf(tHof$uGN zz;9dCWNIoY9lPzcPHZPY(i}|EHi?s&G;ZByGLudxZj$D3l9x1xlQvZ*ha1I71KDDK zzyJ64zyg%0%w(D__5S$o_nrUme?HZEdou=pfBA}8{^AcC#vd@!`HLa)Uc)f`e~y4_ zxMsyzFlS7YVXI;-*faJ*Y$mo4pNTIdW)cg@nWW^|mDEfMW$a3NAv2RfTFy zo@WdZXRp{Zz3z6m&+SKEAMys=LF5fadHtw2)T+0wtDb$*$Pd59{^rfF zr&z7l>cx7wR`v7oF!p4*R1agv%YHqKpITn5cwy@KMdlVOp?$b|c{+^yGrbKvub4tq#%N6wSg6GuEp7p)D@0_hEr|4AB10*@VH@Dzb z>yC76O0gR^tk6c^!bEX#(W|;QVk(8yFvbQ#6aQVfVW=FE)<1k`p;|vwI`r&P+pO_J5)MXELThD zamBmjEzEkZTdvMcEnW_@1@ylVb)4Jg^(;WbikT@hi+?dw4I`n1XTwi+828X;k?a{W zrfbYtt~q0;t+ef)aAR)VwcP~w2X;o>&3rcHe&FNAS<~I+?!MYT6Z7Kk3+`R+-m9@I z=1c;ylkOq+9*HI0Q|<%qyRmmtNPp3N#C=rK(}=y~9(IpNEQ8p~?g!mt?z30pXrm_` zcfZ-4ai`tquf}JxAIJL6^kOfa4s%C2daf5n|CEg0Q_EkD8%D;#@44kly?pRO*}Hha z!BQfuJT7Rh>gcKRlry_ruGF2gsHI;r zT%6+}a%PM5(m9lxQ^mz|e*S>8P^`L+K13LUGkN5I;}#b1u1Y?dAq-hIJ%*)Qh{k?c z&H2*8LHr~jZ!MIoXKT*Gj+{~U=SW@j_dc)J=NlZx94H3 zj5^@#SuCnL=Bgl5?)gZU*C6u@a+^!rl|D5!)sQ|!14HL|6EaF&Wa^f!zxQ={qmyc=clBFDo$91JHqd3c6u3`QE@RA^!9NU_5N{m zab2BLRw&NRDb)VxgulEnabRM>D^@3Tk2wX$Y8Rb)&0!&Lt_Y;0_W&>vcdT1Jd)8A# zL3+)Lb%zUuUFJD1Hsm?|9_1BX)GJ8W@vbIfJ>8nW_A*U#0(3#V7pSGOjD&I(+e8|1 z7HhQ%QH=AXceaQ_FKju}1CS-px1#Y%)ron;?uU9S3N zP66iet=10PqwFr@Ts`c}Vn$lSc=?=16yV6o-m(F{#>aH(`g(eNuX46fsnr(WvN_p1 z=-|9p!h{s7P7&B%-;oYChl?Aja`vFK(|q;hEML^|!f2{oRa?ZK!28ZtYQ^?}crr>? z+-%W=M4J|GB(~7o;JM=`bk~bOIvs0Ttxi2xbC)Y#du0!oaEEHN+iZ}td*4Al9<nx|FvU>M`N zhpFZogz47&hskJSLK_1MGwpr6o|Dr;I_0$BBM>EL&Y-+Tn5vJx%Cs?p+O4};SIR+X zecKkNPTk3z-c}9|gxZ5Nbr(Pw^73GCM%h&sZtn_Ulh`YwJsj7GbD1@qo(x_1wtPibj z6Iz7SYl^8)8duCUjwgp$Zgu+_iNI6?CFh4%M}k3V`CCwLRO*q|P1VOx7VWm`j@NfI zhEQhb8t37TcK(@r8{3e+Yt3k|{UqAHv#~uG;+yfLgY9D+A?A2K79`xBAQ_~BbdU*p zr2k(Eda5?+?_M+9?Dd`?7i@pjSo%Cl-+?*FN#A~`?jSzManHwcMq{{sXE5ycUa=Y@ zsJREvUBU30iNAY;;b3IV((M7>9b}^P5!PL^*5c^>KGy*4)Hi*@Qai9c!WeHHoCw?* zVT{{7?C-j0!*mnf`ZsL#04k{m2_6F2Kzn188EHJ*xaGKw%SJ$o+YHP)wqD&@wOyGG zbjAE<){+H!+k8#P=6GmTt6};?QK27oPlfS%O_k1tac^OYmW7-!Q`% z53JD9YCE#R%%dbWNCrP%MEvyxK*AU{6F1lI?Ih-!G~!eN#74PZC`>&HDydlUAD?Qs zuxU(etu>~(XiOXtSSIr-M`Y=(@+lcp-rCdx59CSC$}R+CR(dwh${IFlb4KP+2EVU9 z0q)^31k~dMPXL68x>xmU3TtyO^<@GD?yWCA;8 z0?W+~guShGA^Ou;vA`YUH+y0h)RQQ=VM$soo@0MF1qs8layQ?}qO{tO&3dB;7#0MI zretIThKX{u;FcG{BqvZ5c^6H^I2M}Y&74dJFSYC8-nT8209Pa3_uUM%R+KtHV zpw}5Y#0Yyxd==`o^H}LHaiLgQ_WUr$-5T~^D&mEzuv{&JGh8Titjlq{E{v;UbJ?R(jOd53r+N7e2HWyyaqrX-=4sq-q-r z5(9;HwT@}Q)Ptv94CB%h^%8SZSkeL)Mm>+{3~u?ND?21{PKbS=k90vZ5rt^i5{D=v zj`!esT>)hJvSz}};9tT{AjE$uD{BtmUk=AFC0DKJbCg| zUNb8bhYLr*@-|uJiCKvsawbYO36Z+QaLS9|C_o-AD(`IhQhqA5O10OB%%($gRy~4% zEc5!%ks_(lh@Z#gI1}p+Og!Rvmli=DgBFh7M`;Ne;L7EwNo37lb|z*g4&luc`KcRm z-~qlj%s>L5>i$JA@nPmAp1h}&s^Kp37b`e^@ny(#6p->Yb^;S@gl17)XGHLC3kiqY z3b;ajuh8sX0p0s{lzV*$AcdvxlkJm|?KEJnPj0>Rn;Qw^NUQtnJ9%DDx9CQ>E2mD$ z-2j?24aTP+LMg@T804)g>3EoMy$j`%x1QBibS#X!JYMTPQKCem61ve6L@KQxDa#}t z*l@-UP+5TH6*C5c^Sq@75d#f)-tu##GEIX}mur9?Y=Ew8B=MwNvysM=shgza zKtH{&(GwU!4>o5u$hsD&ypIIgRSOi7c|Im#d(Gf>6WTM5zkQ%O6KkNp>Z$p(YhMTD z86;LSL7!`m89{o)ARQGXB_)QGOq7yBicpq8x{hPOcZyID^cmt-E1p@}SI^>TB(7qF zyy!vO4j?5dDRT8Ag(FT~!4bpge-J(CbyI+Sf$gTR+n^qqk_lo_d>i8Z!BDU*7zhT> zT7eCkyytp+6=OSTAa4NFq6wPNYUCuAjAGj*hFO!mVYY&2kd)(`rBgu#ZfdhJ5{%$* z_OASFi%~rS!51VOPM4U-mrds!eg1i#l2WZ&!aY(2_Xa_bmR=paSoR@f(B&Y|QP@vf z7UbZ^!{eyh#Y3f91@aFOn@%}TQO@O6JQ0^dnD7zV#vh^73u32$`?VZ6mz;n*wH)M| z&9fU2xsvtQ93WpIg4XF9rdq=$6pXL|0}@x*czj|&?$v)piY?iQHt0)WfR-#0!8c4c zLcXm<#a!w>$>~Suyed$Ndi+12-0Q@w)+@ut`g^xNv3kA=dU6UfwG_a0P?^ohBEvE%-_5Y8lWb7Skg6!ANiHrx8-&u0 zA+^jTUJ_xfP_3?nsZy;7@pH)w?b70Mm@HR8R(rmhXAuk7L5R?#u%Y+gC*+N$mdHt{ z_ew*RT6HcQjAo!zs}(fKiWZANp(0}iJ@6S;_#DA^5w!Xinnz|*W%p76TgcZTY$V+D zAcT0|V@J)zN`RqRhF+jh4z0rophjq;$V|Epr9!2E4b=={N6n;#izuK}Gl;Tm?IPM_ z`!lf%?4EhJYo^0dh+cUFHZ3Wo3JI>4TIxB>yr%@d#PC^{pXm z0Ei#uZh|7v6a(rEfHp`7eGq#imax`i$@PQpXmeq*MXD?`k6$3+xxQQO2NH0aOlXHh zYH?=$K}|!n)7v2_fs4CUS=yp#;>?*64?y$hAkc#chYcDhKz#$eY6vF{BI9Z*Fy;kg zHez+CS_Hp80Ky^ONFc`swm;BF0$0a^q(~4_LCUq44YhA+EJ&@!+*lcsgGO4na%MYj zi8wH51~@!EX4Dfw8Z|Q{Mu3y8W)3*$LrY^zLuG?E3E%Qb^US3Nur+!FR1ozjTTfmC zE=M2sH{!tlXtApwrmD4jQ8m$CvnK*W-;lg6(+!C(!C(upM+m&vmPsL<}@U^G%Ii!7$3KN}1tXl-V8Z zS{$a{;ExDi{| z{t4)caOVx8Uwsqr{0L|3i%)fnMbOm4!xh>sVic!gU*Cdy%SKUkcNHOnFb?` z`=G5)VKFIKbmu1=^x>nOG&(d*Le>E3*FctlchU8QGyyvk#qwLU=X!HnwKs|M0oA)UZNN)bvHFhk4W zkGZ-L(dJBw=$-oOjwO$DIgv_$&1@n|Nce;6)|8Y;u2SSBU1JUc$(*5OR_aGlN?)Ca z5d!7$Gpz*SttplIV>}|28a37**!qU!{(y)cI>HW$iAyemscF%r3sJMFo4l7!vYX_v zq?>Ww%`H3Fb<2y;?Ef^oH)N>qLl_DGcOHb2HwU7sMW3k8BMV|NO0(3j6Z{51ew!wx z&LJG8JtzS@>Q6*!q+FJ|KtNJc$SpO?(5DF~BGP1495hixdQ5PGe~KE1Gf5b{@qrC8xwMTnIB5G?-WF zlfw+RY!o)h1%oyR2}feR-bKKL*=U~&`JrPhM>L~;9z6{$*T>uanKTPRexxpkF&8ZY zqF|Ujtk*s4X*NW9Od@Ot5xr)=%u$QTw@13BYyB!x{ci33u!TkZ3ZlmWB9B8WkHHjY(v6rz}f}E4du)nL_GXQflm&cd&V4=$9g3N zqArKDz4l(oO`$c*rl2SLjZ|XYdAlvHeit2HACtq4S?Z*?galI!=4*2o%WQRsp9T}8zKpUfk8M5ukcvV( zTbQfVX2B-NZLt-qX=9pvOt_z}Q=Cu8>d6v8D9^d@wQ%zH*-@-QH_sO43R}GU2>R<2 zE%e1`lan$XZcS??f7`uRf6aH50oDhg2|UqMpm49OkF9omsVQ1G(db~) z7StwKA`!q9MBMn=P5ko7FismAU+}E@ZKMO4ohzboBGfPe{vN`|FkS-h>S1YHQz~tQ z=CMfF*R5;`2MRPFaz&R!3$A0&aXo2g6^j#@>@gkige!M%y_#Kh)&1;@eGE7@p|Ac9 zC2#xIhr$jw{Du|e!EdeLA0mSL*(4jKSJAZ~lFA{BV-Io_tHo7YgS8u0E{43_NX1%$ z3{70eF-I)cj)CQ(z{>{d&c89XgSG5v2k0Y7r7_UU{b1~%=mhIQb*DW72FbD-$p#p^ zAPLsveDuZ-L&OJFC}qv^=HzuCogjhz@n}a5d(n~Ordnm)}oBWIcdsmLM)B-xS2?rm3$ zXkcwj)lQxkx<$*cDJ?k#s^TZO;K}ozw@AGeDT_bCBfoqqjFXIjlBEhGAx#>*i7e5B z$yU)M$1u|AuD9;&g z<~^v8N@uMcwlh>5P=aUy$1vh4bKD%aGT7qlw?J51v>NsbSyF&$qfiHwB&**6Sl`u> zXSdN-d*7egu;s^5frtx;%=G(k6H>NJ>xL^pP}r>Pm<`zj?zwvWilrQETOue*Tg~gX zNFs2~2?jO&gc)#vaY%bp zkRKbcmgSLH#9Pvh43H|*dXX|j$##%{dgVh)FTxfei{p~Q`|uZ1jGiEiWAGy72PlvC zojKmvyDOUe}fkc^_?i$AEeO^+Gq>~NJn@G)&kqWk#kJ{ zG}u7a7KWl)8#Jd;J+JKt^V)#kgY2euYWYAw934 zJ6OBFRj#*@S~^bIMIC%9WZx|Ni}j(pQQro|P>!p?f8gemu!=C|w_`Npkcn8U!{AmE zb7rtTM+wg`@RD^GxzPw=~g|P3lbI;K1aPobn4>T4%OdVJ(E~ zG|p_duZie)W}w-YcSjhdliStzIJ+hH*apkZc187@n1ks}L=}0LZ9F#)0>Ue0wK2jq zqIR%$r2Xb1a|9u}@|NaZgq1mrS~tv@-iT+QRcs|fC@o&^Q$!s&m4;)i-?QcI2Tt^2 zQMp?ZUvHpd2fF0Fi2Gb{+Si#yOqt&an-%X8j1j=GYnu_jgB{A0(Ju&_t0mY+vd&)+ zkjGPG(v*!33e}{3i%~^DvRQqDphmEp;1Izw095c`=rk?pFj3}WfX%d6IL)Me6uJ3v z&7=)6pUADGFp|7 zBE_dNy)OezrJaL-I%bi-$wA2t{s=r%2H4*MUlapRME;57P9I2E-V@-B5FP@*MfG;f z-iteeyi(TOW3lx5&bQhUxEN`kp@`yd3~Qr-Nhah8goobW3dth9i&2N*0KuIE2MP8N zNbX$>O%d#Eh3;lZ7I+^+_W(4NyHY2^)K{6B)$A^1{#t24ic4!QB=p^kt`e*fd=G)t z{$7M`q|`4k;TH*RS@M^V+vF<#k|oLgsjm@8OMk`ul-@E7>&^Q&47J;olIuvragkG# z(li0Pp$UTo*IG_edx(-th0~3Mk`3FslGQdAZ;d41M^hQf>I#K0Q=s7^3_TIf779zS zg^hBOg@Riv!M37^Hp47VVZB^}RoA(it7RS{&}bh>Ig4_V@_1d?8yRPmJeYkHIw+N^ z9EG5mQG{}M{09M22Gl8cgYW)^6$i9BOf6owUe20U7W?oatBO#DF>vX7a&IcOp*8t3 z6I|yFm@BDGJUYjZ~Ue2I^ttm5{aJ$IAde)2{+{x_f~3 z2GK*FK)ZT;Ht9Yfik+SA9asBiV(z=$JKa5S<%oL;_d$2Bd$%}mBqg@bosd|{eaJoF z9z?0M`yTf`_kM&j;=s{^OZuDCB3@d&2mO+p`lMVXk;8_*SG0}r0S8vqkdAeGaqQm@ zhV9_R;^qDOyN$IY3jf*XPCWm@sl(H!oTs1tVDknQ#|>?KT!no-Y(roJ2y3a9aXk=c z+t?pg(86%SCT0JAEjVb(SH%eb*y;M2{rj~)g@bC^82@x6iav9|@xiK|gK2)Zwf>^Q z`6?aykT&-o=b50>PnMpwE_>2r&p-Bv)#4cfE-< z?l3@xaYM@hIq6ym4~l_^C<(7w1)BmXn7mprNREp^vQ`6p>fG5-Z-cMX`&LdGDe zAfZtngg9U(NCEZ)X~1l~H^@kdzR2wM88N%Z1|sH2nNR1JjQxHuD;-A(2sG}lRoxDEyr4Y+<~$?XqpV-I@QS5`(Zr1E657r z{A1NaZkF}g>Z=A?*)6T~x_vNnhG8*TlxVDXqRgGrMsL(cue9+i@@_hK^>2Fj9vRO- zG@f>!?n3%rv$dK9-g=rQ%(La959y-x{xZ8Oy_X|+t05Se9iOuULt1nE zrd%Mey%+IKQ`n0{nZjcTNSB=E6xK0?*GzUP2h(NBiLcv;$6(ML$KS*n3}sNlkTR0H z7K=J=%NtLr$1s&ZHPZEqc!84sOV@>zT49>t2aW{X!xbhxMX+&AYz~Om6$X?@ypaQO zNQ(*U>xrA2hQwJu$;vQgAX=#A-5C zeXXg2#X44x=KC-**s$+FE6MO5XBVCzcrQU~4MD{oVZ#3;*kU&hwwH1gX6j)ao+)L2 zQ|C5ptxnT(8q;@nO`D$U={=0)Q7>-s;+K&&#uwu;_WbRR0fjcAj=ObVxd&t-`!`M` z{V-h2^$uv$qa9_=p)7vC_eAq3v?f%{ncb?Qj9mRP%5?3Wo$VPM#|&=SovrqZw7126 zxfAWle#!4sAK~kY1RjB0m~vOjHQ8F^TVJ-qR>%pET~cQ&ax{E|!g!?$nXxz^6+i{d zi+V83XhZP=ET$DYPPh(lw)EtKmwoav;wRMv=*}G7e z0!fw~jSrbC8;oe*3a9Hc+2U$8>Cxio>QP=loKZ>=i6gH-Kn{r*Wpa>_i~$oN1abwk zCwNc6y9X!&PlD=@dCCG|$pqmxu6oF_?0N?JHKbj!aKUCGl?tSgK|E(bB4;n<15t}i zt7kDs>b(Sb43XgEd%`4q3W{~OlD&^P#|Z@I{5H$9mr6Ln_cM158KJ#WUIb716blg# zQEuk7pO+_#)$sjhwqRX3bS zP=TZb^~6phK>wKlhyW-86MQ~Kp=tnqz$%2WYt%4Vk3x?DF2UBK^CqJBkJw-wdzcZS zIE2XOVXnAXyLiKvx2isj3QCfqf4+NceFeXG*2BrWQ1F*j{Se1U1e<|Odx)c(dfU-$ zXyDcZMqgw^*cfYakDHjsXY8+^9O-a)p{> z(2Du^2Wg)IKSph+q647KTC_ZdV(_rU@W`?FRs^t_xoVILQX{x(b3hQ}f)#`aXU0VIXOtHi2x~4dJ;z^3DI?`RU2Y>v}~YMyLKA2)2yA3tsFSK zxCrz9jn2I9a!xKj_ngRQL1VT&1*X)aoX|%IzK(%_jM;-fdP_m?M!WscT3Ar0xe)2) zsts705z^3^g=lU zeh}flM!)p0k7kIC0SWg@7|IUz)&C?-7t!ZWp~aq681Mlz4I=*C5=$dCfKos4K71Y} z$iPdc2fQZAaUK!N27`dT!2n?2=g|9)nV981x@9)tBC3@%15nZ+wI7O~ZE$oLxJK;P ztN*UW;8tqGjF98lP9uMuiyYKf{TNSfggSMY|KS?xrzxtNj?7O_qt1}qf8K(LVFF?B zvoM~GG_z2)z~E3!B*9s8UZJcRrfP>VsDHkS8adQF7}ea}xr&?(w)E z!wwn8{EY_Vm{o-4$I!oORBL?1sG7le&}uOl1;!XEIyfsW<b*ssvYx?UA)B_JVhLl{RvZd%RN+v5jg9^&fi%FDyRHr4Xg#zi)=llcwQUf^v|wf5VRyb* z!k1%ca|H_*XQAxtkFL=4v&jQHD~X4lIY=s3vIp7@bCc`lfps&ljSF};YDL?)8s*J0 zMj!;HFbOgb?P-x2d5T5E1WR}&%A3`Xqa8SN^NUXE6cb#6pJwYcmQf;O6mDrR(~dFi ziwr%(&@{nah~{_bn^(3D@3)y0JbVkdbNv}mOJs3q@j8vg>2yI#+AeAjDw!*{)&$2Y@{xJTtnUKxDJ>jU^Y*fZ`ie95av z6hT>B$R|Y`w|ot=B^TG<@rS#h3VP~fQyD}Hkmy30q--nyFuv8q?|@M)qm4SEuPbeK zi_qWFiWEfq_jj6LLnBr6wHrnBeYogK=KDY!D9>w}{X6)^6jV!#xPd(v^_uT#e+)@{ zEp%U+)*Wo7r8wHCm11+0(BT_)u6DiN)U`=`Jtxl>Mdp|C)urgGSCN9LrGpnO)3gZf zBQ=xSg)MYz#r$sO%HekyIzVtIfgp)J4BbT_xInP}76_rlozEXHaPmm~cx;}8dlA6* zOGKHVzee(Iq_jUtqTYk(hJ%DN(fTfl`X&~BnBY-@!vs$fY`nYwoCoO{rXK^?)EsYh z(n9K41m5CTyroWB6#I8zebjfb&F>`m48dm! zK1V<~dxy8@Dx+%z-$U@d02@ROT=OKJqnzb`g}~pT_wv?W%PkuE@4$OmTuk2DclmZ5 zf#1(r{}D`(`qu!Pb}Dkk75a9YJKk#U->}VpOYoxvKSuC*f}bS#PXt{%`AbOKkNWS_ zF%~oq1t4$d7_0s(-}ws!zew;)04+R$utChjixOQh{bPWCk3CXf_eq zmi#}1-6=gk#nTi*o}2Wj3`WGGG7XWyxWu(DC*wP+F8V;h4}>KU_KY`n1mmlH+MBa6 z-uCR2beIAJBj@QsIwd}ty0sD1K+dSqd6|Uv+ky6)wb))M*n##&rM(^Ei3;%sW`!Q8I~Wqr)1eN}Q+z$5u~Xhf zpHuX46*jmKiQFM+^f7Ix?Fx3nhL@tFJAz%e=|%bvaCNjW;)(zNyhv}$hxFDSqz*m? zy1dZkG7Dz&0<3iK-5P$H_z^MwY57@3W<~tkY_WQIp{BgHH-Lj5-bFFe?Kq)o`mW`L z8bv$gQc-)9Ic?G27E-4zoKW|H?j;4mK+s1}C731nGyw_U4gu5`82#@AKSj_( zs!RkXynIEllHaXmC2~=U{6Wa9-(miBg5M>0p5XTgexKkd!B+_WfZz`a{s)1u<$uJ` z{~{2gPUyMsV)QkFKL(gd&Q{Kg@I(TX!2CS({*>U)2>zVls|5d>Ku8B!B%x0K0%=9e zqW=m4e+xHGIHBl&bmOFU*|F)UnK4J<5K4zmruCVVc-nt6NX^;!2e+0^=S}rGd&mh} z&$ZPkZC{iQU*8SG;F+^&W;h`C)0T%y3Ko6HG zIN9x|J{j0wJK4eTEON4oZ*sDO#|qSD@KtdWxLZ@Wb2^>uk}EI$ZJg{L*-LUl%wIwUAz4?R?372fKYG^X8dsp9W#pCA;W5zsmW~#OK7vlDHwY-Z%%`;} zznrKYOnrjjw+Y@P_-TTh1o$8j)qrw7%1n?mgF7}l72;7PlEjVVZn(4C{jam1|Am{G zZ>K9Dy`}bOXTV#0kMb=>tOc@d=dJ{MMjP%8`;P2YYu|A!|IeHN?`f)vy&y`B5tz0G^(w!Qv3o$&6*6rEBj&ZQt}{9g}fU@!PI%T(DVETuW@ zWriqX)A+fUMK{Ud&gDc? zrcY^chdbM?o!j!z$#3c0hIH!GVSWPlG#L9elGP_7cQ<})r%*Otr9M9mEun)CvccLd zNP#rOC)C8IbbAAzM`Ic^g;{)Zr`L@ODc=k7I&u@&HI^P<(iY;d7#C+bw5Xlsy5C>t zXL0&l?s1t$50GRQY4}_(+~e?3VR4UZEM0(Bv=<~i-SO}j?s53g33QYfP@ZkD&&Sbc z8}4y2xW{Ez2V3rOy>O39iF@1^P;y8<8-zB*J&tU_P~;xRIrw~+dt6G~<37#L*&z2n z(^nNW`ZzC3pFnHd+%z`um!Tz&!6og>aF5G%xW_fi^)-@93*sKPUEJeX_N(>naE}{? z3mj&*a*z939lBw-$EAc7*;OB( z-vPI{9)2PYPJcOGAmSjmv%^8ISLL_?g)|}3jE)ASZJR_0+@;&N7k45cioXnXXwaV# z$$+if=qzSxg61#^tL=P=922?aDe6kaa6+)Djh#48%ofBg2W^B=VsEW^x}#h3w8pG) z7V;P(&7c+;6|{Yh(RqS4EhVJYZ!t{~oFe!JL5*NH!65=U-80RdvC3S5%r6Z!$Rh^S6)`? ztran&c)`ENhV$=3K)i)|-o{(#LF*hmg?d2}4(Hy4YKo^q%P(aB#LY7D`~cmc&Mx!+ z76Df!ZfblD;_@Q+7KIft6t+4XuLu$Zg6^L{h+ka6=dGuzRc%GgdnHWhPw{HcBvGC& tvQqn!X`Q1m^{Do}dK}Yc$g#{CJLLnlnfOq~_=o(Ld1R;;aAr93e*sqEP1yhd diff --git a/bayesian_optimization/__pycache__/encoder.cpython-310.pyc b/bayesian_optimization/__pycache__/encoder.cpython-310.pyc deleted file mode 100644 index 520d61cab95193171dc4aa85974eac13f692ec6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15258 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 F{|AKW<2?WX diff --git a/bayesian_optimization/bo.py b/bayesian_optimization/bo.py index 9112037..27b60f4 100644 --- a/bayesian_optimization/bo.py +++ b/bayesian_optimization/bo.py @@ -10,7 +10,6 @@ """ import time -import numpy as np import torch import tqdm import gpytorch diff --git a/bayesian_optimization/chaincomplexembedding.py b/bayesian_optimization/chaincomplexembedding.py index df476d8..32bc292 100644 --- a/bayesian_optimization/chaincomplexembedding.py +++ b/bayesian_optimization/chaincomplexembedding.py @@ -5,8 +5,6 @@ import torch.nn as nn - - def _cum_offsets(sizes: List[int]) -> List[int]: """Compute cumulative offsets for a list of segment sizes.""" offs = [0] diff --git a/bayesian_optimization/encoder.py b/bayesian_optimization/encoder.py index 0fbdcbe..921b88a 100644 --- a/bayesian_optimization/encoder.py +++ b/bayesian_optimization/encoder.py @@ -6,12 +6,9 @@ import numpy as np import torch from toponetx import CombinatorialComplex -from torch_geometric.utils import dense_to_sparse -from typing import List from scipy.sparse import csr_matrix -import numpy as np -from torch_geometric.data import Data, DataLoader +from torch_geometric.data import Data from bayesian_optimization.gadget import * @@ -69,7 +66,7 @@ def __init__(self,n,nx,nz,grakel_use=False,logical=False): self.nz = nz self.grakel_use = grakel_use if grakel_use: - from grakel import Graph + pass self.logical = logical def preprocess_graph(self,adj_matrix: np.ndarray, node_onehots: np.ndarray): """ @@ -181,7 +178,6 @@ def encode(self,hx,hz,lx=None,lz=None): # optional SciPy sparse backend try: - import scipy.sparse as sp _HAS_SCIPY = True except Exception: _HAS_SCIPY = False diff --git a/bayesian_optimization/gadget.py b/bayesian_optimization/gadget.py index d816605..a2007e3 100644 --- a/bayesian_optimization/gadget.py +++ b/bayesian_optimization/gadget.py @@ -1,6 +1,7 @@ from typing import Optional, Sequence, Tuple, List import numpy as np + try: import torch except Exception: diff --git a/bayesian_optimization/gp.py b/bayesian_optimization/gp.py index 0e1b855..d10fa71 100644 --- a/bayesian_optimization/gp.py +++ b/bayesian_optimization/gp.py @@ -10,16 +10,14 @@ from gpytorch.distributions import MultivariateNormal from gpytorch.kernels import RBFKernel, ScaleKernel from gpytorch.likelihoods import GaussianLikelihood -from gpytorch.means import ConstantMean,LinearMean from gpytorch.models import ExactGP from torch import Tensor from gpytorch.priors import LogNormalPrior from gpytorch.constraints import Interval -from gpytorch.kernels import RBFKernel, ScaleKernel -from gpytorch.likelihoods import GaussianLikelihood import math device='cuda' + class NNMean(Mean): def __init__(self, input_size, hidden=256): super().__init__() @@ -185,7 +183,6 @@ def posterior(self, X: torch.Tensor): if __name__ == '__main__': import torch import numpy as np - import matplotlib.pyplot as plt import gpytorch from gpytorch.kernels import ScaleKernel, RBFKernel from gpytorch.distributions import MultivariateNormal diff --git a/bayesian_optimization/graphembedding.py b/bayesian_optimization/graphembedding.py index 0af6876..ec58ae1 100644 --- a/bayesian_optimization/graphembedding.py +++ b/bayesian_optimization/graphembedding.py @@ -1,18 +1,13 @@ -from gpytorch.kernels import Kernel,RBFKernel,ScaleKernel import torch from torch import nn -from torch.nn import ReLU,Sequential -import torch.nn.functional as F -from collections import defaultdict, Counter -import networkx as nx import torch.nn.functional as F from torch_geometric.nn import GCNConv,global_mean_pool -from topomodelx.nn.combinatorial.hmc import HMC from torch_geometric.utils import dense_to_sparse from torch_geometric.data import Data, Batch -from topomodelx.nn.combinatorial.hmc import HMCLayer -from torch_geometric.nn import TransformerConv, global_mean_pool -from torch_geometric.nn import GINConv, global_mean_pool, BatchNorm +from torch_geometric.nn import TransformerConv +from torch_geometric.nn import GINConv + + class GraphRegressor(nn.Module): def __init__(self,n,nx,nz,hidden_channel = 128, embedding_mode='GIN'): super().__init__() @@ -76,13 +71,8 @@ def batch_transform(self, adjacency_matrices): -from torch_geometric.nn import GINConv, global_mean_pool, GraphNorm -import torch.nn.functional as F from torch import nn -import torch import torch.nn as nn -import torch.nn.functional as F -from torch_geometric.nn import GCNConv, global_mean_pool class GCNEmbedder_WithLogicalOperators(nn.Module): def __init__(self,hidden_dim,num_layers): super().__init__() @@ -282,10 +272,8 @@ def forward(self, adjacency_matrices): # 4) global readout out = global_mean_pool(h, batch.batch) return out # [batch_size, hidden_dim] -import torch import torch.nn as nn -import torch.nn.functional as F -from torch_geometric.nn import GATv2Conv, global_mean_pool, global_max_pool, global_add_pool +from torch_geometric.nn import GATv2Conv, global_max_pool, global_add_pool class eGraphTransformerEmbedder(GraphEmbedder): def __init__(self, n, nx, nz, diff --git a/bayesian_optimization/kernels.py b/bayesian_optimization/kernels.py index a9f2ced..30f8788 100644 --- a/bayesian_optimization/kernels.py +++ b/bayesian_optimization/kernels.py @@ -5,19 +5,14 @@ from gpytorch.kernels import Kernel,RBFKernel,ScaleKernel import torch from torch import nn -from torch.nn import ReLU,Sequential import torch.nn.functional as F from collections import defaultdict, Counter -import networkx as nx from torch_geometric.nn import GCNConv,global_mean_pool from topomodelx.nn.combinatorial.hmc import HMC from torch_geometric.utils import dense_to_sparse from torch_geometric.data import Data, Batch -from topomodelx.nn.combinatorial.hmc import HMCLayer - - class GNNEmbedding(nn.Module): """ diff --git a/bayesian_optimization/objective_function.py b/bayesian_optimization/objective_function.py index bd99410..1b19db9 100644 --- a/bayesian_optimization/objective_function.py +++ b/bayesian_optimization/objective_function.py @@ -4,6 +4,7 @@ from code_construction.code_construction import * from evaluation.circuit_level_noise import MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise + # ========================= # Utilities # ========================= @@ -191,8 +192,6 @@ def pl_to_t(self, k, pl): # ObjectiveFunction (with uncertainty) # ========================================= -import math -import numpy as np import torch class ObjectiveFunction: diff --git a/code_construction/__pycache__/code_construction.cpython-310.pyc b/code_construction/__pycache__/code_construction.cpython-310.pyc deleted file mode 100644 index e079a8ce8876db3c4bb6f379ef819d1a0c3df053..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18015 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 None: self.h = h diff --git a/dataset.py b/dataset.py index 7059859..755117f 100644 --- a/dataset.py +++ b/dataset.py @@ -1,19 +1,11 @@ -from typing import Callable, Tuple, List, Dict, Optional import pickle import numpy as np - from evaluation.decoder_based_evaluation import CSS_Evaluator from code_construction.code_construction import CodeConstructor - -import matplotlib.pyplot as plt from torch.utils.data import Dataset -import numpy as np -import random from evaluation.circuit_level_noise import MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise -import numpy as np -import torch class Get_new_points_function(): def __init__(self,method='qc-ldpc-hgp',hyperparameters = {'p': 2, 'q': 6, 'm': 2},encode='None',param={'p':0.5}): self.method = method @@ -103,10 +95,7 @@ def get_k(x): return x.k X = [] y = [] - print('Generating dataset...') - - - + print('Generating dataset...') # target = 200 @@ -143,7 +132,10 @@ def load_dataset(self,l,g,number,path = './data/codes/'): def __getitem__(self,idx): return self.X[idx], self.y[idx] + + from torch.utils.data import Subset + class QECSubset(Subset): def __init__(self, base_dataset, indices): super().__init__(base_dataset,indices) diff --git a/evaluation/__pycache__/circuit_level_noise.cpython-310.pyc b/evaluation/__pycache__/circuit_level_noise.cpython-310.pyc deleted file mode 100644 index b3506a361d47cd6d00d8a0a5c56ac204a132d853..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13510 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 EANR diff --git a/evaluation/__pycache__/css_decode_sim.cpython-310.pyc b/evaluation/__pycache__/css_decode_sim.cpython-310.pyc deleted file mode 100644 index 5c5765afef1c3aeb84431c47f73e2f3915dc0480..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11658 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 diff --git a/evaluation/__pycache__/decoder_based_evaluation.cpython-310.pyc b/evaluation/__pycache__/decoder_based_evaluation.cpython-310.pyc deleted file mode 100644 index 8ca845e72d36500579cc72b1bf38146a8dc69b37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12503 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 ru^V diff --git a/evaluation/__pycache__/distance.cpython-310.pyc b/evaluation/__pycache__/distance.cpython-310.pyc deleted file mode 100644 index 0695124c4ad8ca7e53c61dc90ffbcbabc1363770..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1253 zcmah|OK;Oa5Z>K5sqMr`3Wd@NBvgb%E{#AH;y_D93lb6!0k@UV%JB}VsUPXufzoT# zBmaN{aN|GlOZLhE<;D?lVm7pK6QZ!zyuO|J);pR_JlNCV6csn zeM2RQq!EcJr<4GbY@aDEOS~j4Zb1nwS^mg)Syp%j7UaSJv#`RB?AYNBRya_BvUEN$ z?skcEzffL9TSZ$#TSM!C`>77zXUZGUc+=#Ia1QPH0o^69uJ9$G7+pZQ2usqF-Z6X4 zR>*+yWjwErSOb;^7M^qo7SUsT@nB1OFNu*>bBH$}Evh{kX1SjPAaclZZ3_|mVIst^ zhWo>cNn)BtnszzsbaWY$RgQIyom9e3nq;}^2YIS6s}e)LmrCvQeB}!n26=1khdIV9 zW%x$w1p0Zefd1oorgWsnBxfq-@6M{2!X}wpYl@ol#%eh7>SL_%#wluq%~D0me6Jua zy!jPo7yos1m#G%a3g_N=nx!C^?9G|37ksmlr^-zD^r-$g?S_FLT|MY`!hG|{**zJ( z&DILP3~&9Y@8@Bftam!W`ovf&h6H9d6F&walo?B99;2$F;%&D>Ij*6Aurjaox% F=?^qQ0672v diff --git a/evaluation/__pycache__/non_decoder_based_evaluation.cpython-310.pyc b/evaluation/__pycache__/non_decoder_based_evaluation.cpython-310.pyc deleted file mode 100644 index ca5da58020dfbcf3cc77cf463c467aa88d4ef6b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2864 zcma)8OK%)S5T5RN?Ck8rYbPRc4$+G6$VQPJ-axZ&o{DhPF=D_3@^z1tz%!An`jp_=euWsk!YZuFYOKx{zBgIpIrXc&dRRL${5p4e{ebuj zyulX^7mths0(z_s-CK*pb??!3lr=k1%$w~rkztf%O}UK|#+#i~=n3Bo<9;ZkG-;+C zy^_grGm4`j7n(C`_OmG2@{Zw_MvEx>QNI`S7FG3)DdQSQRHe0z4IHeuk#oLTFu4Ui zdmjild;->@a4X8q4rL~@p4q;|Yz~&;cE{2-v3!S>nfJ`_ORTbCv})?ymkHyNw?S*n z11>}=0uf67!aOD*SCl41EzGVE;APt;Y)u-DDfBhyFB?E|k{f}MQ=o9L4dVq_Z&BgG zj;bVSl<}YoGUAr0tc=GUy(22H{lXA6c>VrpwVTM*_Ub2H-s2()Dh>}PK0iNU~n$V#h88%O`!pekQ zk)Cn{mwl16@t%`bhXDc_@16YyBsX@99a7Mo?$F1yFbgZU7`b2+wloin!jUk`%`*V2 zJI2?SijuSs9a)mDEK7UG%S*Y*=yQ5N56BTIT-dGT?qM~jM}(0hT9k9=uqNwy`EUWD zc?96xeTA8Y2Xd|49?O02ALQDMoHgcTSF~&jvMXcRRaj}{Rc1c{ZDJp^R7P5IXAkDN zakvOxt-vX2xtG^IFbZ$?cJ2X;oZ-N0&b5)DH17Lz@M>y!H4$E?ghlgma{cXVSDPtB zl-LK9Xc?lPG-cG~O$fYhm}Su(Z%y?bqdPEy_9G^@w_u{%G5S^@gj-7^*pY2zXWLLI#*&ftvQCVG7Q8A7c`B)L5NLP`>w>JXRN+REBr@Yh26xs6^=dGi|P7&_A*&tye z?Q&)HM7pWNVMGvZ${iz`qoV9JAg=8ZmpHURF484>mRM$kyb7Z_X^<-H&~GeyI>aa9 zPQ)`tB3e-6`WF~Th~)#oARyTUjFZQJ?7}Xb+{yu0fO1>E?cC9ChtZ;xLmJA>Z|Sm8 zxR41-ZCUtNYOX*^4z$@K;n7hL!&6|UKw)%GR4C=u7?mW)(H-j z6OK8MI|L5r1P89}&T;Glhdt&fX)J#<#TG((4$J5boyFx~g5MFM`Sc5M25dD3;jcH> zH2`%Eg+yn@JkcpmECW-kGDC{E0t@0zAimiPo+23_8Ty_~C8R#o>{I>}`cPa(X3UW@ zP-wiNkn9a0RSUh2j|KjRhn@#4oF^U~b&UuzTs|%7_10eyfrYr`V!}+qE)N3b1(>q? zP$dFY4T9Z%7>{-YCV#O2MA@A<4W+n>3m5`($J7T#01F!kmX!>1uxm8Pl2yH^SKF{~ zq9FutLDw*!);RaCH4baPZ!>gXSvBuI?Lj@`49n(zD%c$e>o95auR zFQ)9I-|Y>=1sH4m>2r@vfIn4!WhzOZcp_HU6aUZE?dtyIZb9lQ-<{qZN|AhU)5 diff --git a/evaluation/__pycache__/qldpc.cpython-310.pyc b/evaluation/__pycache__/qldpc.cpython-310.pyc deleted file mode 100644 index fe4ca332efff7c33bb886285da366810e3628e49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1570 zcmah|OLH4V5T2Qp){?xEWyfzj58DI~wxxtT90I`bBz6> zh11I<;T@vr56+lmlDiBz?ZOqF0S4DP!_9bE$P&(gDYGzwCXPt+2LU-b0(mUR+#ydr z^X{l;fyEhGn8%zf90@2km>m6)LkWw$F)ST(C}X)b4im)B!6fqD6i(n|Yr4#&HBIL| z6vsTe_YwP+lgA9q5}$*4G_)7a;=C*#WnclbxFAb%OqS(1$%}I03j@x>1-M96UXtfv zNitYQ-dgFsBq!FPv!C=+xCg zZLm%?tPOEB^`30iB%5QD=i*7kmDDov0?Sog$15}GJt#h?;Nj6kXKZcco>iOK7fnWWXM&)&I`K&%rP-v)N_*@`gf1Uy|^d{?QJx z9J9E^GxQaRa)d3O6NQY)O}>~h4U?CRGA|g~BO}Nvro}Y342#dxr@c!?DU~g6x8;mD`fUMqb&!#v1itS!@ zM!ly*%86fqkamyzA2{(deC3ogH@Hycp4|+DNJ(2=E?2v4SAA8V%jM-Bf#j@vyf5Loy-Yc=-tfA{m{2IFYDw%7+BrUx_K|`S-q1j z<^8Zv$z75JN%t{Hx`I7&!etouk|h`~+3`x^+#|#O?EBsTp6~9bYA{JNF^J1TM`@u3 z`o0*Xd6kJ=fJLWeF(@b2dV9ajYBWT0I=GqbmNM1%^8rl2f4;fB35z5`je1YP9Wtb* z^R+B@V4*G0+1;BUgFG$LJe>*9&u#DQiCCxm;!a&aUlw3o-Gn7J%R*^c$GVg;F17eL z^TD52KoKOQ5I6ROOen_gaS%F*lemvb=sqDX2|e)DGgmgFqAb!l$~N!aTl8YP(s&x{ zWy}_~K^+hyxSP5TLX$^ik2ZD}&5oRg9#iOD+k1)gGonLaH+SnPh4~W2i643e-;X4A z<<4Fh6dd9Vz8Q9qh3udX13j$-P8s^fQ*|Yz2{?x!Y0eERD&x(BER}J~LMY>;g&s2F zDv?c$SL~!(olw)|JkBDeI6onOeL2nxJ&wm;)>A}8c0oXrxY0qX481dC63=BERp&!ruA@#5j8oPc z=Ez-T{0BeTo=)WJxPTSsabA~;{V1y=EzqX$H0!KSXQP+%>h$LmAMq-pTmW7@&hs*< zGt~Q>Kdhsy{bJ)NFXO(;bFgBOWt=aZY6HGXJetJB>%*%sOabZBwXU_juoh$oCPJQn za4UsOO2MaFX*`WHD5ONZFyGPT(+73rDx2>KD2>w)PId`M$a#>=>W+4y4!ndlv|;Cg zqb`sQ%T(Gw>ipXZ_2C}{&)K?(dsZvnmPJ%vfDLWINIb)^XV#%uT58~vIX9T~dB}k$ zIItFQ!_+M82&K6cs?M}owSy{>0EQM)NsM6|dCU^Iiee4LIS@m4o-di9c^JTkb}MON zI7<`zJ0_??lTX{WVGCh{Vh@Gz(W`|0{|f>W8!@m2QGv2cpakgW$ADYH zZUC-(0nCK)oVT2<*&FsY*cFA4mJzS1K^xjn8hpURJ}bjziMHXY>2RKuG3ViW zHOFIJi>5N;|NWnjFBf(v;FAhe#k;J+)EA70py_AH+ zgb1(iL{p*C2riI?%WCv|fk+S6co#{1e#yWE3dI74`;SE;uUJO0{wz3umiH#O(K6Ym Vc#kPW&n0~)pnYt>lWT0_+&@%qL(BjG diff --git a/evolutionary_algorithm/ea.py b/evolutionary_algorithm/ea.py index 6dfaf2a..bf070dc 100644 --- a/evolutionary_algorithm/ea.py +++ b/evolutionary_algorithm/ea.py @@ -1,13 +1,10 @@ """ This file contains the implementation of the Evolutionary Algorithm on the QEC codes. """ + from pymoo.core.problem import Problem import numpy as np -from pymoo.algorithms.soo.nonconvex.ga import GA -from pymoo.optimize import minimize -# from bayesian_optimization.bo import ObjectiveFunction -from bayesian_optimization.objective_function import ObjectiveFunction -from code_construction.code_construction import CodeConstructor + class CanonicalCSSEvolutionaryOptimization(Problem): def __init__(self,nbits): diff --git a/initial_points.py b/initial_points.py index b8483ac..a7beca1 100644 --- a/initial_points.py +++ b/initial_points.py @@ -1,5 +1,3 @@ -device = 'cuda' -DEVICE = 'cuda' import random import numpy as np import torch @@ -8,23 +6,7 @@ from bayesian_optimization.encoder import * from bayesian_optimization.chaincomplexembedding import * from bayesian_optimization.gp import * -import gpytorch -from gpytorch.mlls import ExactMarginalLogLikelihood -from gpytorch.distributions import MultivariateNormal -from gpytorch.kernels import RBFKernel, MaternKernel, SpectralMixtureKernel, ScaleKernel -import copy -from typing import Dict, Optional, Tuple, Any, List -import torch -import gpytorch -from gpytorch.mlls import ExactMarginalLogLikelihood -from torch.nn.utils import clip_grad_norm_ -import math -from dataclasses import dataclass, asdict -from typing import Tuple, Union, Dict, Any - -from torch.distributions.normal import Normal -from bayesian_optimization.bo import BO_on_QEC def set_all_seeds(seed: int = 42): random.seed(seed) @@ -34,6 +16,8 @@ def set_all_seeds(seed: int = 42): torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False + + class Get_new_points_function(): def __init__(self,method='qc-ldpc-hgp',hyperparameters = {'p': 2, 'q': 6, 'm': 2},encode='None'): self.method = method diff --git a/readme.ipynb b/readme.ipynb index debee45..77ef024 100644 --- a/readme.ipynb +++ b/readme.ipynb @@ -2,8 +2,8 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, - "id": "2bf4c0b3", + "execution_count": null, + "id": "0", "metadata": {}, "outputs": [], "source": [ @@ -14,7 +14,7 @@ }, { "cell_type": "markdown", - "id": "02b23fe3", + "id": "1", "metadata": {}, "source": [ "# Code Generation:\n", @@ -29,31 +29,10 @@ }, { "cell_type": "code", - "execution_count": 2, - "id": "1b326e65", + "execution_count": null, + "id": "2", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "**********************************************************\n", - "3x3 rotated surface code,n:9,k:1\n", - "**********************************************************\n", - "\n", - "**********************************************************\n", - "A random hypergraph product code,n:288,k:8;\n", - "The weight of each row of Hx,Hz is upper bounded by 2*p = 6, the weight of each column of Hx,Hz is upper bounded by 2*q=6;\n", - " n=m(p+q)\n", - "**********************************************************\n", - "\n", - "**********************************************************\n", - "The [144,12,12] gross code,n:144,k:12\n", - "**********************************************************\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "# examples of code construction\n", "from code_construction.code_construction import CodeConstructor\n", @@ -97,7 +76,7 @@ }, { "cell_type": "markdown", - "id": "108b1bb5", + "id": "3", "metadata": {}, "source": [ "# Estimation of logical error rate\n", @@ -139,18 +118,10 @@ }, { "cell_type": "code", - "execution_count": 3, - "id": "eec4eaf6", + "execution_count": null, + "id": "4", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "gross code's logical error rate:0.0614, where physical error rate = 0.05\n" - ] - } - ], + "outputs": [], "source": [ "# An example of calculating logical error rate:\n", "\n", @@ -166,7 +137,7 @@ }, { "cell_type": "markdown", - "id": "93381902", + "id": "5", "metadata": {}, "source": [ "## Circuit-Level Noise\n", @@ -253,18 +224,10 @@ }, { "cell_type": "code", - "execution_count": 4, - "id": "2897f063", + "execution_count": null, + "id": "6", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "the logical error rate under SD6 noise model with physical error rate = 0.007 is :0.00025998559999995674\n" - ] - } - ], + "outputs": [], "source": [ "from evaluation.circuit_level_noise import MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise\n", "\n", @@ -276,7 +239,7 @@ }, { "cell_type": "markdown", - "id": "335d9451", + "id": "7", "metadata": {}, "source": [ "# Generating Datasets\n", @@ -287,117 +250,9 @@ { "cell_type": "code", "execution_count": null, - "id": "97bee7a0", + "id": "8", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Generating dataset...\n", - "code[1],logical error rate:0.1410254440052202\n", - "code[2],logical error rate:0.12649947299894604\n", - "code[3],logical error rate:0.967941400304414\n", - "code[4],logical error rate:0.15046149476972592\n", - "code[5],logical error rate:0.03673792385825858\n", - "code[6],logical error rate:0.12101078011411104\n", - "code[7],logical error rate:0.07121678973297008\n", - "code[8],logical error rate:0.06582500000000002\n", - "code[9],logical error rate:0.003034730748359138\n", - "code[10],logical error rate:0.005826727039220714\n", - "code[11],logical error rate:0.00536174430128844\n", - "code[12],logical error rate:0.9945774338569657\n", - "code[13],logical error rate:0.010879033027873297\n", - "code[14],logical error rate:0.8184577338129496\n", - "code[15],logical error rate:0.13408220850304398\n", - "code[16],logical error rate:0.279169366419165\n", - "code[17],logical error rate:0.9560808298631767\n", - "code[18],logical error rate:0.013755443560430236\n", - "code[19],logical error rate:0.7931416416857697\n", - "code[20],logical error rate:0.027447062080088624\n", - "code[21],logical error rate:0.004351928134076699\n", - "code[22],logical error rate:0.0037791751803828433\n", - "code[23],logical error rate:0.012479502297822687\n", - "code[24],logical error rate:0.9481843317524491\n", - "code[25],logical error rate:0.0025817594311702274\n", - "code[26],logical error rate:0.0011796640000000247\n", - "code[27],logical error rate:0.0016393291999999837\n", - "code[28],logical error rate:0.048956469611415754\n", - "code[29],logical error rate:0.0053214731906285895\n", - "code[30],logical error rate:0.0014194967999999752\n", - "code[31],logical error rate:0.00756628777944468\n", - "code[32],logical error rate:0.04638074943803561\n", - "code[33],logical error rate:0.05000522626858106\n", - "code[34],logical error rate:0.0032476172384378055\n", - "code[35],logical error rate:0.2672155830960815\n", - "code[36],logical error rate:0.003205229455622849\n", - "code[37],logical error rate:0.000619906400000092\n", - "code[38],logical error rate:0.05580730179071092\n", - "code[39],logical error rate:0.00021998879999995946\n", - "code[40],logical error rate:0.8391500424448217\n", - "code[41],logical error rate:0.003308221006165568\n", - "code[42],logical error rate:0.7987309141384097\n", - "code[43],logical error rate:0.7783464415143805\n", - "code[44],logical error rate:0.05047586809004512\n", - "code[45],logical error rate:0.9548888488737559\n", - "code[46],logical error rate:0.0003599680000000438\n", - "code[47],logical error rate:0.9595989096573209\n", - "code[48],logical error rate:0.00023998599999996983\n", - "code[49],logical error rate:0.030304472768204027\n", - "code[50],logical error rate:0.00023998599999996983\n", - "code[51],logical error rate:0.9588865165631469\n", - "code[52],logical error rate:0.0018391571999999412\n", - "code[53],logical error rate:0.004996505071060975\n", - "code[54],logical error rate:0.07561990133507546\n", - "code[55],logical error rate:0.0011796544000000075\n", - "code[56],logical error rate:0.037816806941630365\n", - "code[57],logical error rate:0.138485810584838\n", - "code[58],logical error rate:0.05129434324065196\n", - "code[59],logical error rate:0.06393744506953547\n", - "code[60],logical error rate:0.041977270907829434\n", - "code[61],logical error rate:0.018202314002821907\n", - "code[62],logical error rate:0.8932903291344982\n", - "code[63],logical error rate:0.33225912752557196\n", - "code[64],logical error rate:0.003685083792320354\n", - "code[65],logical error rate:0.09107510864806567\n", - "code[66],logical error rate:0.04744532910132615\n", - "code[67],logical error rate:0.01847170462043246\n", - "code[68],logical error rate:0.8016926651178228\n", - "code[69],logical error rate:0.0016992775999998821\n", - "code[70],logical error rate:0.008615085367169595\n", - "code[71],logical error rate:0.003698369274622082\n", - "code[72],logical error rate:0.00673297830763786\n", - "code[73],logical error rate:0.002565726818282932\n", - "code[74],logical error rate:0.010005861449053022\n", - "code[75],logical error rate:0.802656436122692\n", - "code[76],logical error rate:0.0058518380185600405\n", - "code[77],logical error rate:0.0026283928485606456\n", - "code[78],logical error rate:0.016344182704940535\n", - "code[79],logical error rate:0.9702569565850816\n", - "code[80],logical error rate:0.033485450229133296\n", - "code[81],logical error rate:0.0028130444485817696\n", - "code[82],logical error rate:0.1009154241294663\n", - "code[83],logical error rate:0.008440071856718245\n", - "code[84],logical error rate:0.00661528086095009\n", - "code[85],logical error rate:0.06502997186618154\n", - "code[86],logical error rate:0.0017792439999999576\n", - "code[87],logical error rate:0.23620180206471375\n", - "code[88],logical error rate:0.9551425579896907\n", - "code[89],logical error rate:0.754602033660589\n", - "code[90],logical error rate:0.02057774966300463\n", - "code[91],logical error rate:0.0022577610735975417\n", - "code[92],logical error rate:0.004822783438690137\n", - "code[93],logical error rate:0.06554410970030611\n", - "code[94],logical error rate:0.01991886324226455\n", - "code[95],logical error rate:0.03763442751844592\n", - "code[96],logical error rate:0.9536384976525821\n", - "code[97],logical error rate:0.9629716301843319\n", - "code[98],logical error rate:0.02161412920829664\n", - "code[99],logical error rate:0.0009197884000000656\n", - "code[100],logical error rate:0.9543850806451613\n" - ] - } - ], + "outputs": [], "source": [ "from dataset import QEC_Dataset\n", "l = 12\n", @@ -405,11 +260,11 @@ "dataset = QEC_Dataset(\n", " l=l,\n", " g=g,\n", - " oad = False,\n", + " load = False,\n", " number = 100,\n", " save = True,\n", " path='./data/codes',\n", - " p=0.007\n", + " p=0.007,\n", " noise_model='SD6',\n", " noise_and_decoder_param={\n", " 'rounds':12,\n", @@ -425,31 +280,11 @@ }, { "cell_type": "code", - "execution_count": 6, - "id": "dc36dd53", + "execution_count": null, + "id": "9", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "loading\n", - "successfully loaded\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxYAAAHqCAYAAACZcdjsAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAABAf0lEQVR4nO3deXhU9d3+8XsmCUlYQsgOmrBvKgpERJSgCGVRkU2tCLJIxQ1FkWr1Z4uoFYWCu6CtBrVIW3eLFQVkFaQmgjxoiLIm7JlgCIFsZL6/P3iYh5AQMjlnMhl4v64r18V855zPfM45czK5Ocs4jDFGAAAAAGCB098NAAAAAAh8BAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAHXOE088IYfDUW6sRYsWGjt2rM9fe8eOHXI4HJo3b55nbOzYsWrYsKHPX/sEh8OhJ554otZerya+++47XXHFFWrQoIEcDoc2bNjg75Y8Knv/nGlal8vl467Kq+42DoRlAYATCBYAzlr/+c9/6uwf6HW5tzMpLS3VTTfdpIMHD+r555/Xu+++q+bNm/u7rSo988wz+uSTT/zdhi38vSyfffaZunbtqrCwMCUlJWnq1Kk6duxYteb985//rBtuuEHx8fEBEaABeIdgASAgZGZm6q9//atX8/znP//RtGnTvJqnefPmKiws1G233ebVfN6qqrfCwkI9/vjjPn19K7Zu3aqdO3dqypQpmjBhgkaNGqUmTZr4uy2Pxx9/XIWFheXG/P3H+KmsbGN/LssXX3yhIUOGKDIyUi+//LKGDBmip59+Wvfdd1+15n/88cf13XffqUuXLj7uFIA/BPu7AQCojtDQUJ/WP3bsmNxut+rVq6ewsDCfvtaZ+Pv1z+TAgQOSpMjISP82chrBwcEKDq7bH291fRufzpQpU3TxxRfrq6++8qzjiIgIPfPMM5o0aZI6dOhQ5fzbt29XixYt5HK5FBsbWxstA6hFHLEA4FerV69Wt27dFBYWptatW+v111+vdLpTr7EoLS3VtGnT1LZtW4WFhSk6Olo9e/bU4sWLJR2/LuLVV1+VdPx89hM/0v9dR/GXv/xFL7zwglq3bq3Q0FD99NNPlV5jccK2bdvUv39/NWjQQM2aNdOTTz4pY4zn+eXLl8vhcGj58uXl5ju1ZlW9nRg79RSR9evXa+DAgYqIiFDDhg3Vp08fffvtt+WmmTdvnhwOh7755htNnjxZsbGxatCggYYOHaqcnJzKN8Apvv76a6WkpKhBgwaKjIzU4MGDlZGR4Xl+7NixuuqqqyRJN910kxwOh66++upKa23btk0Oh0PPP/98hefWrFkjh8OhBQsWVDqvMUYxMTGaPHmyZ8ztdisyMlJBQUHKy8vzjD/33HMKDg5WQUGBpIrXJTgcDh05ckRvv/22Z12fer1OXl6exo4dq8jISDVu3Fjjxo3T0aNHq1xXL730UoVeZs2aJYfDUa7vsrIyNWrUSI888ki5nk7dxtXZF3y1LNXx008/6aefftKECRPKBbd77rlHxhh98MEHZ6zRokULy30AqLvq9n/pADir/c///I/69eun2NhYPfHEEzp27JimTp2q+Pj4M877xBNPaPr06frd736nyy67TPn5+UpLS9P333+v3/zmN7rzzju1Z88eLV68WO+++26lNVJTU1VUVKQJEyYoNDRUUVFRcrvdlU5bVlamAQMG6PLLL9eMGTO0aNEiz7nlTz75pFfLXZ3eTvbjjz8qJSVFERERevjhhxUSEqLXX39dV199tVasWKHu3buXm/6+++5TkyZNNHXqVO3YsUMvvPCCJk6cqH/+859Vvs6SJUs0cOBAtWrVSk888YQKCwv18ssv68orr9T333+vFi1a6M4779R5552nZ555Rvfff7+6det22u3VqlUrXXnllZo/f74efPDBcs/Nnz9fjRo10uDBgyud1+Fw6Morr9TKlSs9Yxs3btShQ4fkdDr1zTff6LrrrpMkrVq1Sl26dDntBfbvvvuu530yYcIESVLr1q3LTXPzzTerZcuWmj59ur7//nv97W9/U1xcnJ577rnTrq+UlBS53W6tXr1a119/vacXp9OpVatWeaZbv369CgoK1KtXr9PWqu6+YNeyHDp0SKWlpaft54SwsDDPel2/fr0k6dJLLy03TbNmzXT++ed7ngdwDjMA4CdDhgwxYWFhZufOnZ6xn376yQQFBZlTfz01b97cjBkzxvP4kksuMdddd12V9e+9994KdYwxZvv27UaSiYiIMAcOHKj0udTUVM/YmDFjjCRz3333ecbcbre57rrrTL169UxOTo4xxphly5YZSWbZsmVnrHm63owxRpKZOnWq5/GQIUNMvXr1zNatWz1je/bsMY0aNTK9evXyjKWmphpJpm/fvsbtdnvGH3zwQRMUFGTy8vIqfb0TOnfubOLi4kxubq5n7IcffjBOp9OMHj3aM3ZiOd9///0q6xljzOuvv24kmYyMDM9YSUmJiYmJKbc9KzNz5kwTFBRk8vPzjTHGvPTSS6Z58+bmsssuM4888ogxxpiysjITGRlpHnzwQc98U6dOrbBuGzRoUOnrnZj29ttvLzc+dOhQEx0dXWV/ZWVlJiIiwjz88MPGmOPviejoaHPTTTeZoKAgc/jwYWOMMbNnzzZOp9P8+uuvnnkr28bV3RfsWJarrrrKSDrjz8mvM3PmTCPJZGVlVXjtbt26mcsvv7zK9XWynJycCusAQODjVCgAflFWVqYvv/xSQ4YMUVJSkme8Y8eO6t+//xnnj4yM1I8//qhffvmlxj0MHz7cq/O8J06c6Pm3w+HQxIkTVVJSoiVLltS4hzMpKyvTV199pSFDhqhVq1ae8aZNm+rWW2/V6tWrlZ+fX26eCRMmlDsVKCUlRWVlZdq5c+dpX2fv3r3asGGDxo4dq6ioKM/4xRdfrN/85jf6z3/+U6P+b775ZoWFhWn+/PmesS+//FIul0ujRo2qct4Tfa9Zs0bS8aMBKSkpSklJ8RwR2LRpk/Ly8pSSklKj/k646667Krx2bm5uhXV7MqfTqSuuuMJzVCUjI0O5ubn6wx/+IGOM1q5d6+n7oosuOu01KVb3hZosy6xZs7R48eIz/jz88MOeeU5cEF/Z9U5hYWEVLpgHcO4hWADwi5ycHBUWFqpt27YVnmvfvv0Z53/yySeVl5endu3aqVOnTvr973+vjRs3etVDy5Ytqz2t0+ks94e9JLVr107S8WsofCUnJ0dHjx6tdJ107NhRbrdb2dnZ5cZP/uNUkueOTb/++utpX+dE6Djd67hcLh05csTr/iMjIzVo0CC99957nrH58+frvPPO0zXXXFPlvF27dlX9+vU9IeJEsOjVq5fS0tJUVFTkea5nz55e93aymqwz6fgf7enp6SosLNSqVavUtGlTde3aVZdccomnt9WrV1cZfKzuC6eqzrIkJyerb9++Z/y54IILPPOEh4dLkoqLiyu8ZlFRked5AOcuggWAgNSrVy9t3bpVb731li666CL97W9/U9euXfW3v/2t2jXs/kPodF9kVlZWZuvrnElQUFCl4+akC81r0+jRo7Vt2zatWbNGhw8f1meffaYRI0bI6az6IygkJETdu3fXypUrtWXLFu3bt08pKSnq2bOnSktLtW7dOq1atUodOnSwfIehmq6zE72sXbvWE3wkeY6qbN68WTk5OZaPqHijOsty8OBB7du374w/hw4d8szTtGlTScePbp1q7969atasmc1LAiDQECwA+EVsbKzCw8MrPZUpMzOzWjWioqI0btw4LViwQNnZ2br44ovL3Wmnut9YXB1ut1vbtm0rN/bzzz9L+r873Zz4n+GT7xIkqdJTkKrbW2xsrOrXr1/pOtm8ebOcTqcSExOrVasqJ77g7nSvExMTowYNGtSo9oABAxQbG6v58+fr448/1tGjR6v9PSEpKSn673//qyVLligmJkYdOnRQVFSULrzwQq1atUqrVq2q8qLoE+x8L5zssssuU7169Ty9nAgQvXr10rp167R06VLP49Pxdl+wY1mGDRumpk2bnvFn0qRJnnk6d+4sSUpLSytXa8+ePdq1a5fneQDnLu4KBcAvgoKC1L9/f33yySfKysrynL6RkZGhL7/88ozz5+bmKjo62vO4YcOGatOmTbnTgk78IZyXl2fLdy688soreumllyQd/9/fV155RSEhIerTp4+k43+cBwUFaeXKlRoyZIhnvtdee61Crer2FhQUpH79+unTTz/Vjh07PCFm//79eu+999SzZ09FRERYXramTZuqc+fOevvtt/Xoo496etq0aZO++uqrM14PUZXg4GCNGDFC7733njIyMtSpUyddfPHF1Zo3JSVFTz75pF544QX17NnT80d1SkqK3n33Xe3Zs6daRwMaNGhQIfDZISwsTN26ddOCBQuUlZVV7ohFYWGhXnrpJbVu3drzv/2V8XZfsGNZZs2adcbTvCSVOwpx4YUXqkOHDnrjjTd05513eo6MzJkzRw6HQzfeeKNn2kOHDmnv3r1q2rSpGjdubKlXAIGDYAHAb6ZNm6ZFixYpJSVF99xzj44dO6aXX35ZF1544Rmvl7jgggt09dVXKzk5WVFRUUpLS9MHH3xQ7gLr5ORkSdL999+v/v37KygoSLfcckuNeg0LC9OiRYs0ZswYde/eXV988YU+//xzPfbYY57TcBo3bqybbrpJL7/8shwOh1q3bq2FCxd6vlDuZN709vTTT2vx4sXq2bOn7rnnHgUHB+v1119XcXGxZsyYUaPlqczMmTM1cOBA9ejRQ+PHj/fcbrZx48YVvnPBW6NHj9ZLL72kZcuWVXkL11P16NFDwcHByszM9NxeVTp+BGDOnDmSVK1gkZycrCVLlmj27Nlq1qyZWrZsWeE2vTWVkpKiZ599Vo0bN1anTp0kSXFxcWrfvr0yMzMrfM9EZbzZF+xYlhPvP2/NnDlTN9xwg/r166dbbrlFmzZt0iuvvKLf/e536tixo2e6jz/+WOPGjVNqamq55X/33Xe1c+dOz/dqrFy5Uk8//bQk6bbbbvMcOQMQoPx6TyoA57wVK1aY5ORkU69ePdOqVSszd+7cSm8XeurtZp9++mlz2WWXmcjISBMeHm46dOhg/vznP5uSkhLPNMeOHTP33XefiY2NNQ6Hw1PzxO1fZ86cWaGf091utkGDBmbr1q2mX79+pn79+iY+Pt5MnTrVlJWVlZs/JyfHDB8+3NSvX980adLE3HnnnWbTpk0Vap6uN2Mq3orUGGO+//57079/f9OwYUNTv35907t3b7NmzZpy05y43ex3331Xbvx0t8GtzJIlS8yVV15pwsPDTUREhBk0aJD56aefKq1XndvNnuzCCy80TqfT7Nq1y6v5unXrZiSZdevWecZ27dplJJnExMQK01f2/tm8ebPp1auXCQ8PL3cb1RPTnrhl8Akn1uX27dvP2N/nn39uJJmBAweWG//d735nJJk333yzwjyVbePq7gu+XJbq+Pjjj03nzp1NaGioOf/8883jjz9ebr87+TVPfs8bU/Vtbqvz/gRQtzmM8dPVfACAc0qXLl0UFRXlue4AAHB24eJtAIDPpaWlacOGDRo9erS/WwEA+AhHLAAAPrNp0yalp6dr1qxZcrlc2rZtm8LCwvzdFgDABzhiAQDwmQ8++EDjxo1TaWmpFixYQKgAgLMYRywAAAAAWMYRCwAAAACWESwAAAAAWHbWf0Ge2+3Wnj171KhRI883tgIAAAA4M2OMDh8+rGbNmsnprPqYxFkfLPbs2aPExER/twEAAAAErOzsbJ1//vlVTnPWB4tGjRpJOr4yIiIi/NwNAAAAEDjy8/OVmJjo+Zu6Kmd9sDhx+lNERATBAgAAAKiB6lxSwMXbAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLgv3dAAAEgqysLLlcLtvrxsTEKCkpyfa6AADUNoIFAJxBVlaW2nfoqKLCo7bXDguvr8zNGYQLAEDAI1gAwBm4XC4VFR5V9PUPKSQ60ba6pbnZyl04Sy6Xi2ABAAh4BAsAqKaQ6ESFJrTxdxsAANRJXLwNAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMv8GiymT5+ubt26qVGjRoqLi9OQIUOUmZlZbpqioiLde++9io6OVsOGDTV8+HDt37/fTx0DAAAAqIxfg8WKFSt077336ttvv9XixYtVWlqqfv366ciRI55pHnzwQf373//W+++/rxUrVmjPnj0aNmyYH7sGAAAAcKpgf774okWLyj2eN2+e4uLilJ6erl69eunQoUN688039d577+maa66RJKWmpqpjx4769ttvdfnll/ujbQAAAACnqFPXWBw6dEiSFBUVJUlKT09XaWmp+vbt65mmQ4cOSkpK0tq1a/3SIwAAAICK/HrE4mRut1sPPPCArrzySl100UWSpH379qlevXqKjIwsN218fLz27dtXaZ3i4mIVFxd7Hufn53vqu91u3zQPnMWys7PlcrlsrxsTE6PExETb6/qi382bN8vpdMrpkJwyttV1OiSn0yljDL+fAAB1kjefT3UmWNx7773atGmTVq9ebanO9OnTNW3atArjOTk5KioqslQbONfk5OTorrvvUWlJ8Zkn9lJIvVDNnfOaYmNjbavpy36Tk5MVGR+ukCb2BYtSheu85GQVFRXpwIEDttUFAMAuhw8frva0dSJYTJw4UQsXLtTKlSt1/vnne8YTEhJUUlKivLy8ckct9u/fr4SEhEprPfroo5o8ebLncX5+vhITExUbG6uIiAifLQNwNtq9e7e+XbtG0dc9qJBo+44ulOZmK/fz51VSUqK4uDjb6vqq38Jt6Tq0er4SLhypUDlsq1u8v1D70tMVFhZm63oAAMAuYWFh1Z7Wr8HCGKP77rtPH3/8sZYvX66WLVuWez45OVkhISFaunSphg8fLknKzMxUVlaWevToUWnN0NBQhYaGVhh3Op1yOuvUJSVAnedwOOR2uxUUlaiQ+Da21XWb44dWHQ6Hrfulr/otdmUfP53SSG4bg4Wv1gMAAHbx5vPJr8Hi3nvv1XvvvadPP/1UjRo18lw30bhxY4WHh6tx48YaP368Jk+erKioKEVEROi+++5Tjx49uCMUAAAAUIf4NVjMmTNHknT11VeXG09NTdXYsWMlSc8//7ycTqeGDx+u4uJi9e/fX6+99lotdwoAAACgKn4/FepMwsLC9Oqrr+rVV1+thY4AAAAA1AQn9QIAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMCyYH83AADnuoyMDNtrxsTEKCkpyfa6AACcDsECAPykrOBXyeHQqFGjbK8dFl5fmZszCBcAgFpDsAAAP3EXF0jGKPr6hxQSnWhb3dLcbOUunCWXy0WwAADUGoIFAPhZSHSiQhPa+LsNAAAs4eJtAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZcH+bgDAuSsjI6NO10NFWVlZcrlcttYsLi5WaGiorTUlKSYmRklJSbbXBQBUjmABoNaVFfwqORwaNWqUv1uBF7KystS+Q0cVFR61t7DDKRm3vTUlhYXXV+bmDMIFANQSggWAWucuLpCMUfT1DykkOtG2uoXb0nRo1d9tq4fyXC6XigqP2rrdTmwzu98LpbnZyl04Sy6Xi2ABALWEYAHAb0KiExWa0Ma2eqW52bbVwunZud1ObDO73wsAgNrHxdsAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyvwaLlStXatCgQWrWrJkcDoc++eSTcs+PHTtWDoej3M+AAQP80ywAAACA0/JrsDhy5IguueQSvfrqq6edZsCAAdq7d6/nZ8GCBbXYIQAAAIDqCPbniw8cOFADBw6scprQ0FAlJCTUUkcAAAAAaqLOX2OxfPlyxcXFqX379rr77ruVm5vr75YAAAAAnMKvRyzOZMCAARo2bJhatmyprVu36rHHHtPAgQO1du1aBQUFVTpPcXGxiouLPY/z8/MlSW63W263u1b6BqqSnZ0tl8tle92YmBglJibaWtMYI6fTKadDcsrYVtfpEHVroa4xxtbfe754P/h6HWRkZMgY++pKvtnXAKCu8uZzpE4Hi1tuucXz706dOuniiy9W69attXz5cvXp06fSeaZPn65p06ZVGM/JyVFRUZHPegWqIycnR3fdfY9KS4rPPLGXQuqFau6c1xQbG2tbzaKiIiUnJysyPlwhTez746woMUqHqeuzuqUK13nJySoqKtKBAwdsq+uL94Ov1kFxUZnOS07Wiy++aFvNE3yxrwFAXXX48OFqT1ung8WpWrVqpZiYGG3ZsuW0weLRRx/V5MmTPY/z8/OVmJio2NhYRURE1FarQKV2796tb9euUfR1Dyok2r7/8SzNzVbu58+rpKREcXFxttXdvXu30tPTlXDhSIXKYVvdguyDyqWuz+oW7y/UvvR0hYWF1fn3g8/W7bY9yk1PD5h9DQDqqrCwsGpPG1DBYteuXcrNzVXTpk1PO01oaKhCQ0MrjDudTjmddf6SEpzlHA6H3G63gqISFRLfxra6bnP8UKXD4bD1fX6iX7eR3Db+0XeiX+r6tm4gvB98vQ4CZV8DgLrKm991fg0WBQUF2rJli+fx9u3btWHDBkVFRSkqKkrTpk3T8OHDlZCQoK1bt+rhhx9WmzZt1L9/fz92DQAAAOBUfg0WaWlp6t27t+fxiVOYxowZozlz5mjjxo16++23lZeXp2bNmqlfv3566qmnKj0iAQAAAMB//Bosrr766irv1vHll1/WYjcAAAAAaooTRAEAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWOb1N29v27ZNrVq18kUvAADAZllZWXK5XLbXjYmJUVJSku11AQQur4NFmzZtdNVVV2n8+PG68cYbFRYW5ou+AACARVlZWWrfoaOKCo/aXjssvL4yN2cQLgB4eB0svv/+e6Wmpmry5MmaOHGifvvb32r8+PG67LLLfNEfAACoIZfLpaLCo4q+/iGFRCfaVrc0N1u5C2fJ5XIRLAB4eB0sOnfurBdffFGzZs3SZ599pnnz5qlnz55q166dbr/9dt12222KjY31Ra8AAKAGQqITFZrQxt9tADjL1fji7eDgYA0bNkzvv/++nnvuOW3ZskVTpkxRYmKiRo8erb1799rZJwAAAIA6rMbBIi0tTffcc4+aNm2q2bNna8qUKdq6dasWL16sPXv2aPDgwXb2CQAAAKAO8/pUqNmzZys1NVWZmZm69tpr9c477+jaa6+V03k8o7Rs2VLz5s1TixYt7O4VAAAAQB3ldbCYM2eObr/9do0dO1ZNmzatdJq4uDi9+eablpsDAAAAEBi8Dha//PLLGaepV6+exowZU6OGAAAAAAQer6+xSE1N1fvvv19h/P3339fbb79tS1MAAAAAAovXwWL69OmKiYmpMB4XF6dnnnnGlqYAAAAABBavg0VWVpZatmxZYbx58+bKysqypSkAAAAAgcXrYBEXF6eNGzdWGP/hhx8UHR1tS1MAAAAAAovXwWLEiBG6//77tWzZMpWVlamsrExff/21Jk2apFtuucUXPQIAAACo47y+K9RTTz2lHTt2qE+fPgoOPj672+3W6NGjucYCAAAAOEd5HSzq1aunf/7zn3rqqaf0ww8/KDw8XJ06dVLz5s190R8AAACAAOB1sDihXbt2ateunZ29AAAAAAhQXgeLsrIyzZs3T0uXLtWBAwfkdrvLPf/111/b1hwAAACAwOB1sJg0aZLmzZun6667ThdddJEcDocv+gIAAAAQQLwOFv/4xz/0r3/9S9dee60v+gEAAAAQgLy+3Wy9evXUpk0bX/QCAAAAIEB5HSweeughvfjiizLG+KIfAAAAAAHI61OhVq9erWXLlumLL77QhRdeqJCQkHLPf/TRR7Y1BwAAACAweB0sIiMjNXToUF/0AgAAACBAeR0sUlNTfdEHAAAAgADm9TUWknTs2DEtWbJEr7/+ug4fPixJ2rNnjwoKCmxtDgAAAEBg8PqIxc6dOzVgwABlZWWpuLhYv/nNb9SoUSM999xzKi4u1ty5c33RJwAAAIA6zOsjFpMmTdKll16qX3/9VeHh4Z7xoUOHaunSpbY2BwAAACAweH3EYtWqVVqzZo3q1atXbrxFixbavXu3bY0BAAAACBxeH7Fwu90qKyurML5r1y41atTIlqYAAAAABBavg0W/fv30wgsveB47HA4VFBRo6tSpuvbaa+3sDQAAAECA8PpUqFmzZql///664IILVFRUpFtvvVW//PKLYmJitGDBAl/0CAAAAKCO8zpYnH/++frhhx/0j3/8Qxs3blRBQYHGjx+vkSNHlruYGwAAAMC5w+tgIUnBwcEaNWqU3b0AAAAACFBeB4t33nmnyudHjx5d42YAAAAABCavg8WkSZPKPS4tLdXRo0dVr1491a9fn2ABAAAAnIO8vivUr7/+Wu6noKBAmZmZ6tmzJxdvAwAAAOcor4NFZdq2batnn322wtEMAAAAAOcGW4KFdPyC7j179thVDgAAAEAA8foai88++6zcY2OM9u7dq1deeUVXXnmlbY0BAAAACBxeB4shQ4aUe+xwOBQbG6trrrlGs2bNsqsvAAAAAAHE62Dhdrt90QcAAACAAGbbNRYAAAAAzl1eH7GYPHlytaedPXu2t+UBAAAABCCvg8X69eu1fv16lZaWqn379pKkn3/+WUFBQeratatnOofDYV+XAAAAAOo0r4PFoEGD1KhRI7399ttq0qSJpONfmjdu3DilpKTooYcesr1JAAAAAHWb19dYzJo1S9OnT/eECklq0qSJnn76ae4KBQAAAJyjvA4W+fn5ysnJqTCek5Ojw4cP29IUAAAAgMDidbAYOnSoxo0bp48++ki7du3Srl279OGHH2r8+PEaNmyYL3oEAAAAUMd5fY3F3LlzNWXKFN16660qLS09XiQ4WOPHj9fMmTNtbxAAAABA3ed1sKhfv75ee+01zZw5U1u3bpUktW7dWg0aNLC9OQAAAACBwetgccLevXu1d+9e9erVS+Hh4TLGcItZnFWysrLkcrlsrZmRkWFrPaAqdr/feP8CAKridbDIzc3VzTffrGXLlsnhcOiXX35Rq1atNH78eDVp0oQ7Q+GskJWVpfYdOqqo8Ki/WwG8Vlbwq+RwaNSoUf5uBQBwDvE6WDz44IMKCQlRVlaWOnbs6Bn/7W9/q8mTJxMscFZwuVwqKjyq6OsfUkh0om11C7el6dCqv9tWD6iMu7hAMob3LwCgVnkdLL766it9+eWXOv/888uNt23bVjt37rStMaAuCIlOVGhCG9vqleZm21YLOBPevwCA2uT17WaPHDmi+vXrVxg/ePCgQkNDbWkKAAAAQGDxOlikpKTonXfe8Tx2OBxyu92aMWOGevfubWtzAAAAAAKD16dCzZgxQ3369FFaWppKSkr08MMP68cff9TBgwf1zTff+KJHAAAAAHWc10csLrroIv3888/q2bOnBg8erCNHjmjYsGFav369Wrdu7YseAQAAANRxXh2xKC0t1YABAzR37lz9v//3/3zVEwAAAIAA49URi5CQEG3cuNFXvQAAAAAIUF6fCjVq1Ci9+eabvugFAAAAQIDy+uLtY8eO6a233tKSJUuUnJysBg0alHt+9uzZtjUHAAAAIDBUK1hs3LhRF110kZxOpzZt2qSuXbtKkn7++edy0zkcDvs7BAAAAFDnVStYdOnSRXv37lVcXJx27typ7777TtHR0ZZffOXKlZo5c6bS09O1d+9effzxxxoyZIjneWOMpk6dqr/+9a/Ky8vTlVdeqTlz5qht27aWXxsAAACAfap1jUVkZKS2b98uSdqxY4fcbrctL37kyBFdcsklevXVVyt9fsaMGXrppZc0d+5crVu3Tg0aNFD//v1VVFRky+sDAAAAsEe1jlgMHz5cV111lZo2bSqHw6FLL71UQUFBlU67bdu2ar/4wIEDNXDgwEqfM8bohRde0OOPP67BgwdLkt555x3Fx8frk08+0S233FLt1wEAAADgW9UKFm+88YaGDRumLVu26P7779cdd9yhRo0a+bSx7du3a9++ferbt69n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uuiii/Too4/q6NGjtdAdTudM2419LXAtX75ccXFxat++ve6++27l5ub6uyVUYfv27dq3b1+5fa1x48bq3r07+1otWLt2rSIjI3XppZd6xvr27Sun06l169ZVOS+fa74VEF+Qh7rnkUce0SuvvKKjR4/q8ssv18KFC0877b59+1SvXr0K563Gx8dr3759Pu4Up9qyZYtefvll/eUvf6lyultvvVXNmzdXs2bNtHHjRj3yyCPKzMzURx99VEud4mTV2W7sa4FpwIABGjZsmFq2bKmtW7fqscce08CBA7V27VoFBQX5uz1U4sT+FB8fX26cfa127Nu3T3FxceXGgoODFRUVVeX653PN9zhiAUnSH/7whwoXNJ36s3nzZs/0v//977V+/Xp99dVXCgoK0ujRo2X4rsVa5e02k6Tdu3drwIABuummm3THHXdUWX/ChAnq37+/OnXqpJEjR+qdd97Rxx9/rK1bt/pysc56vt5usF9Ntpk3brnlFt1www3q1KmThgwZooULF+q7777T8uXL7VuIc5Cvtxvs5+ttxuea73HEApKkhx56SGPHjq1ymlatWnn+HRMTo5iYGLVr104dO3ZUYmKivv32W/Xo0aPCfAkJCSopKVFeXl65/0ndv3+/EhIS7FqEc46322zPnj3q3bu3rrjiCr3xxhtev1737t0lHf+f89atW3s9P47z5XZjX/MNb7eZVa1atVJMTIy2bNmiPn362Fb3XOPL7XZif9q/f7+aNm3qGd+/f786d+5co5qo/jZLSEjQgQMHyo0fO3ZMBw8e9Op3HZ9r9iNYQJIUGxur2NjYGs3rdrslScXFxZU+n5ycrJCQEC1dulTDhw+XJGVmZiorK6vSIILq8Wab7d69W71791ZycrJSU1PldHp/sHLDhg2SVO5DFN7z5XZjX/MNK78fa2LXrl3Kzc1lX7PIl9utZcuWSkhI0NKlSz1BIj8/X+vWrfP6jmD4P9XdZj169FBeXp7S09OVnJwsSfr666/ldrs9YaE6+FzzAX9fPY7A8u2335qXX37ZrF+/3uzYscMsXbrUXHHFFaZ169amqKjIGGPMrl27TPv27c26des88911110mKSnJfP311yYtLc306NHD9OjRw1+LcU7ZtWuXadOmjenTp4/ZtWuX2bt3r+fn5GlO3mZbtmwxTz75pElLSzPbt283n376qWnVqpXp1auXvxbjnFOT7WYM+5q/7dy506xfv95MmzbNNGzY0Kxfv96sX7/eHD582DNN+/btzUcffWSMMebw4cNmypQpZu3atWb79u1myZIlpmvXrqZt27ae36nwPW+3mzHGPPvssyYyMtJ8+umnZuPGjWbw4MGmZcuWprCw0B+LcM4ZMGCA6dKli1m3bp1ZvXq1adu2rRkxYoTneT7X/INgAa9s3LjR9O7d20RFRZnQ0FDTokULc9ddd5ldu3Z5ptm+fbuRZJYtW+YZKywsNPfcc49p0qSJqV+/vhk6dGi5P5DgO6mpqUZSpT8nnLrNsrKyTK9evTzbuU2bNub3v/+9OXTokJ+W4txTk+1mDPuav40ZM6bSbXbyNpJkUlNTjTHGHD161PTr18/ExsaakJAQ07x5c3PHHXeYffv2+WcBzlHebjdjjt9y9o9//KOJj483oaGhpk+fPiYzM7P2mz9H5ebmmhEjRpiGDRuaiIgIM27cuHJBkM81/3AYwxW3AAAAAKzhrlAAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAC/adGihV544QV/twEAsAHBAgAAAIBlBAsAAAAAlhEsAAA18sYbb6hZs2Zyu93lxgcPHqzbb79dW7du1eDBgxUfH6+GDRuqW7duWrJkyWnr7dixQw6HQxs2bPCM5eXlyeFwaPny5Z6xTZs2aeDAgWrYsKHi4+N12223yeVy2b14AAAvESwAADVy0003KTc3V8uWLfOMHTx4UIsWLdLIkSNVUFCga6+9VkuXLtX69es1YMAADRo0SFlZWTV+zby8PF1zzTXq0qWL0tLStGjRIu3fv18333yzHYsEALAg2N8NAAACU5MmTTRw4EC999576tOnjyTpgw8+UExMjHr37i2n06lLLrnEM/1TTz2ljz/+WJ999pkmTpxYo9d85ZVX1KVLFz3zzDOesbfeekuJiYn6+eef1a5dO2sLBQCoMY5YAABqbOTIkfrwww9VXFwsSZo/f75uueUWOZ1OFRQUaMqUKerYsaMiIyPVsGFDZWRkWDpi8cMPP2jZsmVq2LCh56dDhw6SpK1bt9qyTACAmuGIBQCgxgYNGiRjjD7//HN169ZNq1at0vPPPy9JmjJlihYvXqy//OUvatOmjcLDw3XjjTeqpKSk0lpO5/H/6zLGeMZKS0vLTVNQUKBBgwbpueeeqzB/06ZN7VosAEANECwAADUWFhamYcOGaf78+dqyZYvat2+vrl27SpK++eYbjR07VkOHDpV0PBTs2LHjtLViY2MlSXv37lWXLl0kqdyF3JLUtWtXffjhh2rRooWCg/kIA4C6hFOhAACWjBw5Up9//rneeustjRw50jPetm1bffTRR9qwYYN++OEH3XrrrRXuIHWy8PBwXX755Xr22WeVkZGhFStW6PHHHy83zb333quDBw9qxIgR+u6777R161Z9+eWXGjdunMrKyny2jACAMyNYAAAsueaaaxQVFaXMzEzdeuutnvHZs2erSZMmuuKKKzRo0CD179/fczTjdN566y0dO3ZMycnJeuCBB/T000+Xe75Zs2b65ptvVFZWpn79+qlTp0564IEHFBkZ6TmVCgDgHw5z8smsAAAAAFAD/PcOAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAsv8PJ/c64COMmPMAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "from sklearn.preprocessing import StandardScaler\n", "import torch\n", "from dataset import QEC_Dataset\n", "import matplotlib.pyplot as plt\n", @@ -457,7 +292,7 @@ "l = 6\n", "g = 3\n", "para_dict = {'l':l,'g':g}\n", - "total_dataset = QEC_Dataset(l,g,load = True,number = 300,save = True,path='./')\n", + "total_dataset = QEC_Dataset(l,g,load = True,number = 300,save = True,path='./data/codes/')\n", "total_dataset.X = np.array(total_dataset.X)\n", "\n", "# My current dataset uses word error rate: WER = 1-(1-LER)^{1/k}\n", @@ -484,9 +319,6 @@ "# total_dataset.y = torch.from_numpy(sy.transform(total_dataset.y.numpy().reshape(-1,1))).float()\n", "\n", "\n", - "import random\n", - "from torch.utils.data import Subset\n", - "import pandas as pd\n", "def plot_distribution(y, bin_width):\n", " bins = np.arange(np.min(y), np.max(y) + bin_width, bin_width)\n", " \n", @@ -503,40 +335,18 @@ }, { "cell_type": "code", - "execution_count": 11, - "id": "dda166d7", + "execution_count": null, + "id": "10", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "loading\n", - "successfully loaded\n", - "[tensor(0.0556), tensor(0.0578), tensor(0.0834), tensor(0.0991), tensor(0.1054), tensor(0.1148), tensor(0.1176), tensor(0.1217), tensor(0.1243), tensor(0.1262), tensor(0.1310), tensor(0.1346), tensor(0.1421), tensor(0.1456), tensor(0.1616), tensor(0.1640), tensor(0.1651), tensor(0.2039), tensor(0.2059), tensor(0.2192), tensor(0.2226), tensor(0.2229), tensor(0.2388), tensor(0.2408), tensor(0.2449), tensor(0.2535), tensor(0.2548), tensor(0.2606), tensor(0.2663), tensor(0.2769), tensor(0.2811), tensor(0.2828), tensor(0.2837), tensor(0.2918), tensor(0.2930), tensor(0.2951), tensor(0.2951), tensor(0.2953), tensor(0.2977), tensor(0.3002), tensor(0.3029), tensor(0.3087), tensor(0.3106), tensor(0.3148), tensor(0.3171), tensor(0.3178), tensor(0.3180), tensor(0.3202), tensor(0.3242), tensor(0.3358), tensor(0.3368), tensor(0.3631), tensor(0.3655), tensor(0.3673), tensor(0.3676), tensor(0.3866), tensor(0.3928), tensor(0.3947), tensor(0.3979), tensor(0.3984), tensor(0.3988), tensor(0.4011), tensor(0.4041), tensor(0.4058), tensor(0.4144), tensor(0.4173), tensor(0.4185), tensor(0.4193), tensor(0.4316), tensor(0.4318), tensor(0.4330), tensor(0.4410), tensor(0.4418), tensor(0.4460), tensor(0.4580), tensor(0.4743), tensor(0.4815), tensor(0.4929), tensor(0.4943), tensor(0.4971), tensor(0.4988), tensor(0.4992), tensor(0.4998), tensor(0.5065), tensor(0.5079), tensor(0.5091), tensor(0.5093), tensor(0.5174), tensor(0.5207), tensor(0.5233), tensor(0.5349), tensor(0.5359), tensor(0.5359), tensor(0.5477), tensor(0.5527), tensor(0.5538), tensor(0.5548), tensor(0.5654), tensor(0.5681), tensor(0.5700), tensor(0.5809), tensor(0.5820), tensor(0.5926), tensor(0.5930), tensor(0.5958), tensor(0.5990), tensor(0.5994), tensor(0.5997), tensor(0.6072), tensor(0.6237), tensor(0.6273), tensor(0.6328), tensor(0.6331), tensor(0.6387), tensor(0.6404), tensor(0.6443), tensor(0.6531), tensor(0.6634), tensor(0.6637), tensor(0.6741), tensor(0.6809), tensor(0.6847), tensor(0.6920), tensor(0.6930), tensor(0.6984), tensor(0.6996), tensor(0.7019), tensor(0.7033), tensor(0.7096), tensor(0.7097), tensor(0.7179), tensor(0.7182), tensor(0.7188), tensor(0.7222), tensor(0.7241), tensor(0.7243), tensor(0.7247), tensor(0.7259), tensor(0.7261), tensor(0.7283), tensor(0.7288), tensor(0.7356), tensor(0.7470), tensor(0.7471), tensor(0.7528), tensor(0.7552), tensor(0.7559), tensor(0.7596), tensor(0.7617), tensor(0.7661), tensor(0.7729), tensor(0.7730), tensor(0.7732), tensor(0.7757), tensor(0.7800), tensor(0.7809), tensor(0.7833), tensor(0.7860), tensor(0.7884), tensor(0.7918), tensor(0.7989), tensor(0.8050), tensor(0.8059), tensor(0.8062), tensor(0.8112), tensor(0.8171), tensor(0.8208), tensor(0.8256), tensor(0.8275), tensor(0.8314), tensor(0.8323), tensor(0.8333), tensor(0.8337), tensor(0.8374), tensor(0.8429), tensor(0.8432), tensor(0.8503), tensor(0.8520), tensor(0.8530), tensor(0.8530), tensor(0.8545), tensor(0.8563), tensor(0.8565), tensor(0.8588), tensor(0.8607), tensor(0.8627), tensor(0.8630), tensor(0.8638), tensor(0.8646), tensor(0.8648), tensor(0.8654), tensor(0.8696), tensor(0.8733), tensor(0.8751), tensor(0.8760), tensor(0.8803), tensor(0.8855), tensor(0.8911), tensor(0.8923), tensor(0.8928), tensor(0.8932), tensor(0.8996), tensor(0.9007), tensor(0.9013), tensor(0.9030), tensor(0.9044), tensor(0.9059), tensor(0.9069), tensor(0.9078), tensor(0.9084), tensor(0.9108), tensor(0.9131), tensor(0.9134), tensor(0.9166), tensor(0.9190), tensor(0.9222), tensor(0.9234), tensor(0.9300), tensor(0.9312), tensor(0.9345), tensor(0.9355), tensor(0.9368), tensor(0.9377), tensor(0.9389), tensor(0.9413), tensor(0.9422), tensor(0.9482), tensor(0.9496), tensor(0.9499), tensor(0.9513), tensor(0.9514), tensor(0.9514), tensor(0.9516), tensor(0.9531), tensor(0.9542), tensor(0.9559), tensor(0.9607), tensor(0.9665), tensor(0.9689), tensor(0.9695), tensor(0.9709), tensor(0.9712), tensor(0.9718), tensor(0.9722), tensor(0.9729), tensor(0.9731), tensor(0.9738), tensor(0.9747), tensor(0.9758), tensor(0.9772), tensor(0.9787), tensor(0.9823), tensor(0.9824), tensor(0.9826), tensor(0.9829), tensor(0.9835), tensor(0.9847), tensor(0.9848), tensor(0.9851), tensor(0.9862), tensor(0.9865), tensor(0.9870), tensor(0.9872), tensor(0.9881), tensor(0.9889), tensor(0.9900), tensor(0.9917), tensor(0.9918), tensor(0.9929), tensor(0.9933), tensor(0.9948), tensor(0.9954), tensor(0.9955), tensor(0.9973), tensor(0.9976), tensor(0.9982), tensor(0.9983), tensor(0.9985), tensor(0.9986), tensor(0.9988), tensor(0.9989), tensor(0.9991), tensor(0.9995), tensor(0.9997), tensor(0.9998), tensor(0.9998), tensor(0.9998), tensor(0.9998), tensor(0.9999), tensor(1.0000), tensor(1.0000), tensor(1.0000), tensor(1.0000), tensor(1.0000), tensor(1.0000), tensor(1.0000), tensor(1.0000), tensor(1.0000), tensor(1.), tensor(1.)]\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxYAAAHqCAYAAACZcdjsAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAABADklEQVR4nO3deXgUVd728bs7ZGMJgexogMguoEAEZFWEIaAiEQYfFWWRER1BWcQFHxVBFAFBBlDRGY3LICru44JCRBZBhIgyCAZZA4SEBAwJQkJIn/cPXvohJEA61Z1Oh+/nunJd9umqU7/q0233TdWpshljjAAAAADAAru3CwAAAADg+wgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAqnaeeeko2m61YW8OGDTVs2DCPb3v37t2y2Wx64403nG3Dhg1TzZo1Pb7t02w2m5566qkK2155rF+/Xp07d1aNGjVks9n0888/e7skp9LePxdaNjs728NVFVfWMfaFfQGA0wgWAKqsL7/8stL+QK/MtV1IYWGhBg0apMOHD+uFF17Q22+/rQYNGni7rPN69tln9cknn3i7DLfw9r589tlnateunYKCglS/fn1NmjRJJ0+eLNO6zzzzjG666SZFRUX5RIAG4BqCBQCfkJqaqn/+858urfPll19q8uTJLq3ToEEDHT9+XHfeeadL67nqfLUdP35cjz/+uEe3b8WOHTu0Z88eTZgwQSNHjtQdd9yhOnXqeLssp8cff1zHjx8v1ubtH+NnszLG3tyXr776SomJiQoNDdW8efOUmJioqVOn6v777y/T+o8//rjWr1+vtm3berhSAN5QzdsFAEBZBAYGerT/kydPyuFwKCAgQEFBQR7d1oV4e/sXcvDgQUlSaGiodws5h2rVqqlatcr99VbZx/hcJkyYoCuuuELffPON8zUOCQnRs88+qzFjxqh58+bnXX/Xrl1q2LChsrOzFRERURElA6hAHLEA4FWrV69W+/btFRQUpEaNGumVV14pdbmz51gUFhZq8uTJatKkiYKCghQWFqauXbtq6dKlkk7Ni3jxxRclnTqf/fSf9H/zKJ5//nnNmTNHjRo1UmBgoLZs2VLqHIvTdu7cqYSEBNWoUUP16tXTlClTZIxxPv/dd9/JZrPpu+++K7be2X2er7bTbWefIrJx40b17dtXISEhqlmzpnr27Kkffvih2DJvvPGGbDabvv/+e40fP14RERGqUaOGbr75ZmVlZZU+AGf59ttv1a1bN9WoUUOhoaHq37+/tm7d6nx+2LBhuuaaayRJgwYNks1m07XXXltqXzt37pTNZtMLL7xQ4rk1a9bIZrNp0aJFpa5rjFF4eLjGjx/vbHM4HAoNDZWfn59ycnKc7dOnT1e1atV09OhRSSXnJdhsNv3555968803na/12fN1cnJyNGzYMIWGhqp27doaPny4jh07dt7Xau7cuSVqmTVrlmw2W7G6i4qKVKtWLT3yyCPFajp7jMvyWfDUvpTFli1btGXLFo0cObJYcLvvvvtkjNEHH3xwwT4aNmxouQ4AlVfl/icdAFXaf//7X/Xu3VsRERF66qmndPLkSU2aNElRUVEXXPepp57StGnT9Le//U0dOnRQbm6uNmzYoJ9++kl/+ctfdM899yg9PV1Lly7V22+/XWofSUlJys/P18iRIxUYGKi6devK4XCUumxRUZH69Omjq6++WjNmzNCSJUuc55ZPmTLFpf0uS21n+vXXX9WtWzeFhITo4Ycflr+/v1555RVde+21WrFihTp27Fhs+fvvv1916tTRpEmTtHv3bs2ZM0ejR4/We++9d97tLFu2TH379tVll12mp556SsePH9e8efPUpUsX/fTTT2rYsKHuueceXXLJJXr22Wf1wAMPqH379uccr8suu0xdunTRwoULNW7cuGLPLVy4ULVq1VL//v1LXddms6lLly5auXKls23Tpk06cuSI7Ha7vv/+e91www2SpFWrVqlt27bnnGD/9ttvO98nI0eOlCQ1atSo2DK33HKL4uLiNG3aNP3000/617/+pcjISE2fPv2cr1e3bt3kcDi0evVq3Xjjjc5a7Ha7Vq1a5Vxu48aNOnr0qLp3737Ovsr6WXDXvhw5ckSFhYXnrOe0oKAg5+u6ceNGSdJVV11VbJl69erp0ksvdT4P4CJmAMBLEhMTTVBQkNmzZ4+zbcuWLcbPz8+c/b+nBg0amKFDhzofX3nlleaGG244b/+jRo0q0Y8xxuzatctIMiEhIebgwYOlPpeUlORsGzp0qJFk7r//fmebw+EwN9xwgwkICDBZWVnGGGOWL19uJJnly5dfsM9z1WaMMZLMpEmTnI8TExNNQECA2bFjh7MtPT3d1KpVy3Tv3t3ZlpSUZCSZXr16GYfD4WwfN26c8fPzMzk5OaVu77Q2bdqYyMhIc+jQIWfbL7/8Yux2uxkyZIiz7fR+Ll68+Lz9GWPMK6+8YiSZrVu3OttOnDhhwsPDi41naWbOnGn8/PxMbm6uMcaYuXPnmgYNGpgOHTqYRx55xBhjTFFRkQkNDTXjxo1zrjdp0qQSr22NGjVK3d7pZe+6665i7TfffLMJCws7b31FRUUmJCTEPPzww8aYU++JsLAwM2jQIOPn52fy8vKMMcbMnj3b2O1288cffzjXLW2My/pZcMe+XHPNNUbSBf/O3M7MmTONJJOWllZi2+3btzdXX331eV+vM2VlZZV4DQD4Pk6FAuAVRUVF+vrrr5WYmKj69es721u0aKGEhIQLrh8aGqpff/1Vv//+e7lrGDhwoEvneY8ePdr53zabTaNHj9aJEye0bNmyctdwIUVFRfrmm2+UmJioyy67zNkeExOj22+/XatXr1Zubm6xdUaOHFnsVKBu3bqpqKhIe/bsOed2Dhw4oJ9//lnDhg1T3bp1ne1XXHGF/vKXv+jLL78sV/233HKLgoKCtHDhQmfb119/rezsbN1xxx3nXfd03WvWrJF06mhAt27d1K1bN+cRgc2bNysnJ0fdunUrV32n3XvvvSW2fejQoRKv7Znsdrs6d+7sPKqydetWHTp0SI8++qiMMVq7dq2z7latWp1zTorVz0J59mXWrFlaunTpBf8efvhh5zqnJ8SXNt8pKCioxIR5ABcfggUAr8jKytLx48fVpEmTEs81a9bsgutPmTJFOTk5atq0qVq3bq2HHnpImzZtcqmGuLi4Mi9rt9uL/bCXpKZNm0o6NYfCU7KysnTs2LFSX5MWLVrI4XBo7969xdrP/HEqyXnFpj/++OOc2zkdOs61nezsbP35558u1x8aGqp+/frpnXfecbYtXLhQl1xyia677rrzrtuuXTtVr17dGSJOB4vu3btrw4YNys/Pdz7XtWtXl2s7U3leM+nUj/aUlBQdP35cq1atUkxMjNq1a6crr7zSWdvq1avPG3ysfhbOVpZ9iY+PV69evS74d/nllzvXCQ4OliQVFBSU2GZ+fr7zeQAXL4IFAJ/UvXt37dixQ6+//rpatWqlf/3rX2rXrp3+9a9/lbkPd/8QOteNzIqKity6nQvx8/Mrtd2cMdG8Ig0ZMkQ7d+7UmjVrlJeXp88++0y33Xab7PbzfwX5+/urY8eOWrlypbZv366MjAx169ZNXbt2VWFhodatW6dVq1apefPmlq8wVN7X7HQta9eudQYfSc6jKr/99puysrIsH1FxRVn25fDhw8rIyLjg35EjR5zrxMTESDp1dOtsBw4cUL169dy8JwB8DcECgFdEREQoODi41FOZUlNTy9RH3bp1NXz4cC1atEh79+7VFVdcUexKO2W9Y3FZOBwO7dy5s1jbtm3bJP3flW5O/8vwmVcJklTqKUhlrS0iIkLVq1cv9TX57bffZLfbFRsbW6a+zuf0De7OtZ3w8HDVqFGjXH336dNHERERWrhwoT7++GMdO3aszPcJ6datm3788UctW7ZM4eHhat68uerWrauWLVtq1apVWrVq1XknRZ/mzvfCmTp06KCAgABnLacDRPfu3bVu3TolJyc7H5+Lq58Fd+zLgAEDFBMTc8G/MWPGONdp06aNJGnDhg3F+kpPT9e+ffuczwO4eHFVKABe4efnp4SEBH3yySdKS0tznr6xdetWff311xdc/9ChQwoLC3M+rlmzpho3blzstKDTP4RzcnLccs+F+fPna+7cuZJO/evv/Pnz5e/vr549e0o69ePcz89PK1euVGJionO9l156qURfZa3Nz89PvXv31qeffqrdu3c7Q0xmZqbeeecdde3aVSEhIZb3LSYmRm3atNGbb76piRMnOmvavHmzvvnmmwvOhzifatWq6bbbbtM777yjrVu3qnXr1rriiivKtG63bt00ZcoUzZkzR127dnX+qO7WrZvefvttpaenl+loQI0aNUoEPncICgpS+/bttWjRIqWlpRU7YnH8+HHNnTtXjRo1cv5rf2lc/Sy4Y19mzZp1wdO8JBU7CtGyZUs1b95cr776qu655x7nkZGXX35ZNptNf/3rX53LHjlyRAcOHFBMTIxq165tqVYAvoNgAcBrJk+erCVLlqhbt2667777dPLkSc2bN08tW7a84HyJyy+/XNdee63i4+NVt25dbdiwQR988EGxCdbx8fGSpAceeEAJCQny8/PTrbfeWq5ag4KCtGTJEg0dOlQdO3bUV199pS+++EKPPfaY8zSc2rVra9CgQZo3b55sNpsaNWqkzz//3HlDuTO5UtvUqVO1dOlSde3aVffdd5+q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W7euCQwMNA0bNjT33nuv2bdvn3OZXbt2GUlm+fLlzrbjx4+b++67z9SpU8dUr17d3HzzzcV+AKFiJSUlGUml/p129jimpaWZ7t27O8e+cePG5qGHHjJHjhzx0l5c3MozhsbwWaxshg4dWuoYnjlmkkxSUpIxxphjx46Z3r17m4iICOPv728aNGhg7r77bucXKbzD1XE05tQlZ5944gkTFRVlAgMDTc+ePU1qamrFFw9jjDGHDh0yt912m6lZs6YJCQkxw4cPLxYM+U6snObNm2fq169vAgICTIcOHcwPP/zgfO6aa64xQ4cOLbb8+++/b5o2bWoCAgJMy5YtzRdffFHBFV+YzRhmLQMAAACwpkpcFQoAAACAdxEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAIDXNGzYUHPmzPF2GQAANyBYAAAAALCMYAEAAADAMoIFAKBcXn31VdWrV08Oh6NYe//+/XXXXXdpx44d6t+/v6KiolSzZk21b99ey5YtO2d/u3fvls1m088//+xsy8nJkc1m03fffeds27x5s/r27auaNWsqKipKd955p7Kzs929ewAAFxEsAADlMmjQIB06dEjLly93th0+fFhLlizR4MGDdfToUV1//fVKTk7Wxo0b1adPH/Xr109paWnl3mZOTo6uu+46tW3bVhs2bNCSJUuUmZmpW265xR27BACwoJq3CwAA+KY6deqob9++euedd9SzZ09J0gcffKDw8HD16NFDdrtdV155pXP5p59+Wh9//LE+++wzjR49ulzbnD9/vtq2batnn33W2fb6668rNjZW27ZtU9OmTa3tFACg3DhiAQAot8GDB+vDDz9UQUGBJGnhwoW69dZbZbfbdfToUU2YMEEtWrRQaGioatasqa1bt1o6YvHLL79o+fLlqlmzpvOvefPmkqQdO3a4ZZ8AAOXDEQsAQLn169dPxhh98cUXat++vVatWqUXXnhBkjRhwgQtXbpUzz//vBo3bqzg4GD99a9/1YkTJ0rty24/9W9dxhhnW2FhYbFljh49qn79+mn69Okl1o+JiXHXbgEAyoFgAQAot6CgIA0YMEALFy7U9u3b1axZM7Vr106S9P3332vYsGG6+eabJZ0KBbt37z5nXxEREZKkAwcOqG3btpJUbCK3JLVr104ffvihGjZsqGrV+AoDgMqEU6EAAJYMHjxYX3zxhV5//XUNHjzY2d6kSRN99NFH+vnnn/XLL7/o9ttvL3EFqTMFBwfr6quv1nPPPaetW7dqxYoVevzxx4stM2rUKB0+fFi33Xab1q9frx07dujrr7/W8OHDVVRU5LF9BABcGMECAGDJddddp7p16yo1NVW33367s3327NmqU6eOOnfurH79+ikhIcF5NONcXn/9dZ08eVLx8fEaO3aspk6dWuz5evXq6fvvv1dRUZF69+6t1q1ba+zYsQoNDXWeSgUA8A6bOfNkVgAAAAAoB/55BwAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYNn/A/Oy6AjgvzK/AAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "from sklearn.preprocessing import StandardScaler\n", "import torch\n", "from dataset import QEC_Dataset\n", - "import matplotlib.pyplot as plt\n", "# SD6 noise, physical error rate = 0.007\n", "l = 12\n", "g = 6\n", "para_dict = {'l':l,'g':g}\n", - "total_dataset = QEC_Dataset(l,g,load = True,number = 300,save = True,path='./')\n", + "total_dataset = QEC_Dataset(l,g,load = True,number = 300,save = True,path='./data/codes/')\n", "total_dataset.X = np.array(total_dataset.X)\n", "\n", "# My current dataset uses word error rate: WER = 1-(1-LER)^{1/k}\n", @@ -563,9 +373,6 @@ "# total_dataset.y = torch.from_numpy(sy.transform(total_dataset.y.numpy().reshape(-1,1))).float()\n", "\n", "\n", - "import random\n", - "from torch.utils.data import Subset\n", - "import pandas as pd\n", "def plot_distribution(y, bin_width):\n", " bins = np.arange(np.min(y), np.max(y) + bin_width, bin_width)\n", " \n", @@ -584,7 +391,7 @@ ], "metadata": { "kernelspec": { - "display_name": "QEC2", + "display_name": "qec", "language": "python", "name": "python3" }, @@ -598,7 +405,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.13" + "version": "3.10.19" } }, "nbformat": 4, diff --git a/readme2.ipynb b/readme2.ipynb index 6d0e914..033941f 100644 --- a/readme2.ipynb +++ b/readme2.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "a5da2dc7", + "id": "0", "metadata": {}, "source": [ "# An instruction of how to set up the environmnet\n", @@ -13,30 +13,9 @@ { "cell_type": "code", "execution_count": null, - "id": "5691cce4", + "id": "1", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Channels:\n", - " - \n", - "Platform: linux-64\n", - "Collecting package metadata (repodata.json): done\n", - "Solving environment: failed\n", - "\n", - "PackagesNotFoundError: The following packages are missing from the target environment:\n", - "\n", - " - llvmlite\n", - "\n", - "\n", - "\n", - "CondaError: Run 'conda init' before 'conda activate'\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "!conda create -n qec python=3.10 numpy scipy matplotlib pandas\n", "!conda activate qec" @@ -44,7 +23,7 @@ }, { "cell_type": "markdown", - "id": "427e396e", + "id": "2", "metadata": {}, "source": [ "## Important !" @@ -52,7 +31,7 @@ }, { "cell_type": "markdown", - "id": "47d2badf", + "id": "3", "metadata": {}, "source": [ "Before installing anything else, we need to install cuda and pytorch=2.6.0; This depends on the gpu and the operation system. See [[https://pytorch.org/get-started/locally/]] for torch and [[https://docs.nvidia.com/cuda/]] for cuda" @@ -60,7 +39,7 @@ }, { "cell_type": "markdown", - "id": "2d0586a4", + "id": "4", "metadata": {}, "source": [ "## pip libraries:\n", @@ -87,394 +66,9 @@ { "cell_type": "code", "execution_count": null, - "id": "916978bc", + "id": "5", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: torch==2.6.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (2.6.0)\n", - "Requirement already satisfied: filelock in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (3.17.0)\n", - "Requirement already satisfied: typing-extensions>=4.10.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (4.12.2)\n", - "Requirement already satisfied: networkx in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (2.6.3)\n", - "Requirement already satisfied: jinja2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (3.1.5)\n", - "Requirement already satisfied: fsspec in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (2025.2.0)\n", - "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (12.4.127)\n", - "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (12.4.127)\n", - "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (12.4.127)\n", - "Requirement already satisfied: nvidia-cudnn-cu12==9.1.0.70 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (9.1.0.70)\n", - "Requirement already satisfied: nvidia-cublas-cu12==12.4.5.8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (12.4.5.8)\n", - "Requirement already satisfied: nvidia-cufft-cu12==11.2.1.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (11.2.1.3)\n", - "Requirement already satisfied: nvidia-curand-cu12==10.3.5.147 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (10.3.5.147)\n", - "Requirement already satisfied: nvidia-cusolver-cu12==11.6.1.9 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (11.6.1.9)\n", - "Requirement already satisfied: nvidia-cusparse-cu12==12.3.1.170 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (12.3.1.170)\n", - "Requirement already satisfied: nvidia-cusparselt-cu12==0.6.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (0.6.2)\n", - "Requirement already satisfied: nvidia-nccl-cu12==2.21.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (2.21.5)\n", - "Requirement already satisfied: nvidia-nvtx-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (12.4.127)\n", - "Requirement already satisfied: nvidia-nvjitlink-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (12.4.127)\n", - "Requirement already satisfied: triton==3.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (3.2.0)\n", - "Requirement already satisfied: sympy==1.13.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch==2.6.0) (1.13.1)\n", - "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sympy==1.13.1->torch==2.6.0) (1.3.0)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from jinja2->torch==2.6.0) (3.0.2)\n", - "Requirement already satisfied: torch-geometric in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (2.6.1)\n", - "Requirement already satisfied: aiohttp in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (3.11.12)\n", - "Requirement already satisfied: fsspec in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (2025.2.0)\n", - "Requirement already satisfied: jinja2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (3.1.5)\n", - "Requirement already satisfied: numpy in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (1.26.4)\n", - "Requirement already satisfied: psutil>=5.8.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (6.1.1)\n", - "Requirement already satisfied: pyparsing in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (3.2.1)\n", - "Requirement already satisfied: requests in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (2.32.3)\n", - "Requirement already satisfied: tqdm in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch-geometric) (4.67.1)\n", - "Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (2.4.6)\n", - "Requirement already satisfied: aiosignal>=1.1.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (1.3.2)\n", - "Requirement already satisfied: async-timeout<6.0,>=4.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (5.0.1)\n", - "Requirement already satisfied: attrs>=17.3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (25.1.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (1.5.0)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (6.1.0)\n", - "Requirement already satisfied: propcache>=0.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (0.2.1)\n", - "Requirement already satisfied: yarl<2.0,>=1.17.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->torch-geometric) (1.18.3)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from jinja2->torch-geometric) (3.0.2)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->torch-geometric) (3.4.1)\n", - "Requirement already satisfied: idna<4,>=2.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->torch-geometric) (3.10)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->torch-geometric) (2.3.0)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->torch-geometric) (2025.1.31)\n", - "Requirement already satisfied: typing-extensions>=4.1.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from multidict<7.0,>=4.5->aiohttp->torch-geometric) (4.12.2)\n", - "Requirement already satisfied: gpytorch in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (1.14)\n", - "Requirement already satisfied: botorch in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.13.0)\n", - "Requirement already satisfied: ax in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.36.0)\n", - "Requirement already satisfied: pymatching in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (2.2.1)\n", - "Requirement already satisfied: pymoo in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.6.1.3)\n", - "Requirement already satisfied: qecsim in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (1.0b9)\n", - "Requirement already satisfied: qldpc in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.2.5)\n", - "Requirement already satisfied: jaxtyping in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from gpytorch) (0.2.37)\n", - "Requirement already satisfied: mpmath<=1.3,>=0.19 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from gpytorch) (1.3.0)\n", - "Requirement already satisfied: scikit-learn in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from gpytorch) (1.6.1)\n", - "Requirement already satisfied: scipy>=1.6.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from gpytorch) (1.13.1)\n", - "Requirement already satisfied: linear-operator>=0.6 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from gpytorch) (0.6)\n", - "Requirement already satisfied: typing_extensions in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from botorch) (4.12.2)\n", - "Requirement already satisfied: pyre_extensions in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from botorch) (0.0.32)\n", - "Requirement already satisfied: torch>=2.0.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from botorch) (2.6.0)\n", - "Requirement already satisfied: pyro-ppl>=1.8.4 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from botorch) (1.9.1)\n", - "Requirement already satisfied: multipledispatch in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from botorch) (1.0.0)\n", - "Requirement already satisfied: threadpoolctl in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from botorch) (3.5.0)\n", - "Requirement already satisfied: sanic in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (24.12.0)\n", - "Requirement already satisfied: Sanic-GraphQl in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (1.1.0)\n", - "Requirement already satisfied: Sanic-Cors in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.2.0)\n", - "Requirement already satisfied: sanic-compress in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (0.1.1)\n", - "Requirement already satisfied: SQLAlchemy in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (1.4.54)\n", - "Requirement already satisfied: sqlalchemy-utils in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (0.41.2)\n", - "Requirement already satisfied: alembic in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (1.14.1)\n", - "Requirement already satisfied: psycopg2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.9.10)\n", - "Requirement already satisfied: graphene in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.1.9)\n", - "Requirement already satisfied: graphene-sqlalchemy in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.3.0)\n", - "Requirement already satisfied: graphql-core in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.3.2)\n", - "Requirement already satisfied: graphql-relay in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.0.1)\n", - "Requirement already satisfied: graphql-ws in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (0.4.4)\n", - "Requirement already satisfied: aiocache[redis] in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (0.12.3)\n", - "Requirement already satisfied: aiopubsub in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (3.0.0)\n", - "Requirement already satisfied: APScheduler in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (3.11.0)\n", - "Requirement already satisfied: pyyaml in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (6.0.2)\n", - "Requirement already satisfied: ruamel.yaml in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (0.18.10)\n", - "Requirement already satisfied: typing in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (3.7.4.3)\n", - "Requirement already satisfied: ujson in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (5.10.0)\n", - "Requirement already satisfied: loguru in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (0.7.3)\n", - "Requirement already satisfied: docopt in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (0.6.2)\n", - "Requirement already satisfied: dotmap in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (1.3.30)\n", - "Requirement already satisfied: aiosmtplib in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (4.0.0)\n", - "Requirement already satisfied: sanic-jwt in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (1.8.0)\n", - "Requirement already satisfied: passlib in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (1.7.4)\n", - "Requirement already satisfied: markdown2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.5.3)\n", - "Requirement already satisfied: python-dateutil in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.9.0.post0)\n", - "Requirement already satisfied: gunicorn in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (23.0.0)\n", - "Requirement already satisfied: rsa in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (4.9)\n", - "Requirement already satisfied: stripe in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (11.6.0)\n", - "Requirement already satisfied: pyquery in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (2.0.1)\n", - "Requirement already satisfied: lxml in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (5.3.1)\n", - "Requirement already satisfied: openpyxl in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (3.1.5)\n", - "Requirement already satisfied: aiohttp in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (3.11.12)\n", - "Requirement already satisfied: aiofiles in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ax) (24.1.0)\n", - "Requirement already satisfied: numpy==1.* in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymatching) (1.26.4)\n", - "Requirement already satisfied: networkx in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymatching) (2.6.3)\n", - "Requirement already satisfied: matplotlib in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymatching) (3.8.4)\n", - "Requirement already satisfied: autograd>=1.4 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (1.7.0)\n", - "Requirement already satisfied: cma==3.2.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (3.2.2)\n", - "Requirement already satisfied: alive-progress in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (3.2.0)\n", - "Requirement already satisfied: dill in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (0.3.9)\n", - "Requirement already satisfied: Deprecated in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (1.2.18)\n", - "Requirement already satisfied: click>=7 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qecsim) (8.1.8)\n", - "Requirement already satisfied: cvxpy>=1.3.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qldpc) (1.7.3)\n", - "Requirement already satisfied: diskcache>=5.0.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qldpc) (5.6.3)\n", - "Requirement already satisfied: galois>=0.4.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qldpc) (0.4.6)\n", - "Requirement already satisfied: ldpc>=2.1.8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qldpc) (2.2.8)\n", - "Requirement already satisfied: platformdirs>=4.0.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qldpc) (4.3.6)\n", - "Collecting scipy>=1.6.0 (from gpytorch)\n", - " Using cached scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\n", - "Requirement already satisfied: stim>=1.15.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qldpc) (1.15.0)\n", - "Requirement already satisfied: sympy>=1.12 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qldpc) (1.13.1)\n", - "Requirement already satisfied: osqp>=1.0.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from cvxpy>=1.3.2->qldpc) (1.0.4)\n", - "Requirement already satisfied: clarabel>=0.5.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from cvxpy>=1.3.2->qldpc) (0.11.1)\n", - "Requirement already satisfied: scs>=3.2.4.post1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from cvxpy>=1.3.2->qldpc) (3.2.9)\n", - "Requirement already satisfied: numba<0.62,>=0.55 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from galois>=0.4.2->qldpc) (0.61.2)\n", - "Requirement already satisfied: tqdm in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc>=2.1.8->qldpc) (4.67.1)\n", - "Requirement already satisfied: pytest in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc>=2.1.8->qldpc) (8.3.5)\n", - "Requirement already satisfied: sinter in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc>=2.1.8->qldpc) (1.15.0)\n", - "Requirement already satisfied: contourpy>=1.0.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching) (1.2.1)\n", - "Requirement already satisfied: cycler>=0.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching) (0.12.1)\n", - "Requirement already satisfied: fonttools>=4.22.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching) (4.56.0)\n", - "Requirement already satisfied: kiwisolver>=1.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching) (1.4.8)\n", - "Requirement already satisfied: packaging>=20.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching) (24.2)\n", - "Requirement already satisfied: pillow>=8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching) (11.1.0)\n", - "Requirement already satisfied: pyparsing>=2.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching) (3.2.1)\n", - "Requirement already satisfied: opt-einsum>=2.3.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyro-ppl>=1.8.4->botorch) (3.4.0)\n", - "Requirement already satisfied: pyro-api>=0.1.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyro-ppl>=1.8.4->botorch) (0.1.2)\n", - "Requirement already satisfied: six>=1.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from python-dateutil->ax) (1.17.0)\n", - "Requirement already satisfied: filelock in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (3.17.0)\n", - "Requirement already satisfied: jinja2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (3.1.5)\n", - "Requirement already satisfied: fsspec in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (2025.2.0)\n", - "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (12.4.127)\n", - "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (12.4.127)\n", - "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (12.4.127)\n", - "Requirement already satisfied: nvidia-cudnn-cu12==9.1.0.70 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (9.1.0.70)\n", - "Requirement already satisfied: nvidia-cublas-cu12==12.4.5.8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (12.4.5.8)\n", - "Requirement already satisfied: nvidia-cufft-cu12==11.2.1.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (11.2.1.3)\n", - "Requirement already satisfied: nvidia-curand-cu12==10.3.5.147 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (10.3.5.147)\n", - "Requirement already satisfied: nvidia-cusolver-cu12==11.6.1.9 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (11.6.1.9)\n", - "Requirement already satisfied: nvidia-cusparse-cu12==12.3.1.170 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (12.3.1.170)\n", - "Requirement already satisfied: nvidia-cusparselt-cu12==0.6.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (0.6.2)\n", - "Requirement already satisfied: nvidia-nccl-cu12==2.21.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (2.21.5)\n", - "Requirement already satisfied: nvidia-nvtx-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (12.4.127)\n", - "Requirement already satisfied: nvidia-nvjitlink-cu12==12.4.127 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (12.4.127)\n", - "Requirement already satisfied: triton==3.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from torch>=2.0.1->botorch) (3.2.0)\n", - "Requirement already satisfied: redis>=4.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiocache[redis]->ax) (5.2.1)\n", - "Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (2.4.6)\n", - "Requirement already satisfied: aiosignal>=1.1.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (1.3.2)\n", - "Requirement already satisfied: async-timeout<6.0,>=4.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (5.0.1)\n", - "Requirement already satisfied: attrs>=17.3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (25.1.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (1.5.0)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (6.1.0)\n", - "Requirement already satisfied: propcache>=0.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (0.2.1)\n", - "Requirement already satisfied: yarl<2.0,>=1.17.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->ax) (1.18.3)\n", - "Requirement already satisfied: Mako in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from alembic->ax) (1.3.9)\n", - "Requirement already satisfied: greenlet!=0.4.17 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from SQLAlchemy->ax) (3.1.1)\n", - "Requirement already satisfied: about-time==4.2.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from alive-progress->pymoo) (4.2.1)\n", - "Requirement already satisfied: grapheme==0.6.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from alive-progress->pymoo) (0.6.0)\n", - "Requirement already satisfied: tzlocal>=3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from APScheduler->ax) (5.3)\n", - "Requirement already satisfied: wrapt<2,>=1.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from Deprecated->pymoo) (1.17.2)\n", - "Requirement already satisfied: aniso8601<=7,>=3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from graphene->ax) (7.0.0)\n", - "Requirement already satisfied: promise<3,>=2.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from graphql-core->ax) (2.3)\n", - "Requirement already satisfied: rx<2,>=1.6 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from graphql-core->ax) (1.6.3)\n", - "Requirement already satisfied: singledispatch<4,>=3.4.0.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from graphene-sqlalchemy->ax) (3.7.0)\n", - "Requirement already satisfied: wadler-lindig>=0.1.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from jaxtyping->gpytorch) (0.1.3)\n", - "Requirement already satisfied: et-xmlfile in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from openpyxl->ax) (2.0.0)\n", - "Requirement already satisfied: cssselect>=1.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyquery->ax) (1.2.0)\n", - "Requirement already satisfied: typing-inspect in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyre_extensions->botorch) (0.9.0)\n", - "Requirement already satisfied: pyasn1>=0.1.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from rsa->ax) (0.6.1)\n", - "Requirement already satisfied: ruamel.yaml.clib>=0.2.7 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ruamel.yaml->ax) (0.2.12)\n", - "Requirement already satisfied: sanic-routing>=23.12.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic->ax) (23.12.0)\n", - "Requirement already satisfied: httptools>=0.0.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic->ax) (0.6.4)\n", - "Requirement already satisfied: uvloop>=0.15.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic->ax) (0.21.0)\n", - "Requirement already satisfied: websockets>=10.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic->ax) (15.0)\n", - "Requirement already satisfied: html5tagger>=1.2.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic->ax) (1.3.0)\n", - "Requirement already satisfied: tracerite>=1.0.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic->ax) (1.1.1)\n", - "Requirement already satisfied: setuptools>=70.1.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic->ax) (75.8.0)\n", - "Requirement already satisfied: graphql-server-core>=1.0.dev in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from Sanic-GraphQl->ax) (2.0.0)\n", - "Requirement already satisfied: pytest-runner in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from Sanic-GraphQl->ax) (6.0.1)\n", - "Requirement already satisfied: pyjwt<3.0.0,>=2.1.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sanic-jwt->ax) (2.10.1)\n", - "Requirement already satisfied: joblib>=1.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from scikit-learn->gpytorch) (1.4.2)\n", - "Requirement already satisfied: requests>=2.20 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from stripe->ax) (2.32.3)\n", - "Requirement already satisfied: cffi in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from clarabel>=0.5.0->cvxpy>=1.3.2->qldpc) (1.15.1)\n", - "Requirement already satisfied: llvmlite<0.45,>=0.44.0dev0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from numba<0.62,>=0.55->galois>=0.4.2->qldpc) (0.44.0)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests>=2.20->stripe->ax) (3.4.1)\n", - "Requirement already satisfied: idna<4,>=2.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests>=2.20->stripe->ax) (3.10)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests>=2.20->stripe->ax) (2.3.0)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests>=2.20->stripe->ax) (2025.1.31)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from jinja2->torch>=2.0.1->botorch) (3.0.2)\n", - "Requirement already satisfied: exceptiongroup>=1.0.0rc8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc>=2.1.8->qldpc) (1.2.2)\n", - "Requirement already satisfied: iniconfig in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc>=2.1.8->qldpc) (2.0.0)\n", - "Requirement already satisfied: pluggy<2,>=1.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc>=2.1.8->qldpc) (1.5.0)\n", - "Requirement already satisfied: tomli>=1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc>=2.1.8->qldpc) (2.2.1)\n", - "Requirement already satisfied: mypy-extensions>=0.3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from typing-inspect->pyre_extensions->botorch) (1.0.0)\n", - "Requirement already satisfied: pycparser in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from cffi->clarabel>=0.5.0->cvxpy>=1.3.2->qldpc) (2.23)\n", - "Using cached scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (37.7 MB)\n", - "Installing collected packages: scipy\n", - " Attempting uninstall: scipy\n", - " Found existing installation: scipy 1.13.1\n", - " Uninstalling scipy-1.13.1:\n", - " Successfully uninstalled scipy-1.13.1\n", - "\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", - "gensim 4.3.3 requires scipy<1.14.0,>=1.7.0, but you have scipy 1.15.3 which is incompatible.\n", - "karateclub 1.3.3 requires numpy<1.23.0, but you have numpy 1.26.4 which is incompatible.\n", - "quits 0.0.1 requires networkx>=2.8.8, but you have networkx 2.6.3 which is incompatible.\u001b[0m\u001b[31m\n", - "\u001b[0mSuccessfully installed scipy-1.15.3\n", - "Requirement already satisfied: toponetx in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.2.0)\n", - "Requirement already satisfied: topomodelx in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.0.1)\n", - "Requirement already satisfied: topoembedx in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.0.2.dev131)\n", - "Requirement already satisfied: networkx in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (2.6.3)\n", - "Requirement already satisfied: numpy in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (1.26.4)\n", - "Requirement already satisfied: pandas in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (1.3.5)\n", - "Requirement already satisfied: pyarrow in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (19.0.0)\n", - "Requirement already satisfied: requests in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (2.32.3)\n", - "Requirement already satisfied: scipy in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (1.15.3)\n", - "Requirement already satisfied: trimesh in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (4.6.2)\n", - "Requirement already satisfied: typing-extensions in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from toponetx) (4.12.2)\n", - "Requirement already satisfied: tqdm in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topomodelx) (4.67.1)\n", - "Requirement already satisfied: scikit-learn in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topomodelx) (1.6.1)\n", - "Requirement already satisfied: matplotlib in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topomodelx) (3.8.4)\n", - "Requirement already satisfied: gudhi in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topomodelx) (3.10.1)\n", - "Requirement already satisfied: pyg-nightly in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topomodelx) (2.7.0.dev20250213)\n", - "Requirement already satisfied: karateclub in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topoembedx) (1.3.3)\n", - "Requirement already satisfied: pyrandwalk in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topoembedx) (1.1)\n", - "Requirement already satisfied: pygsp in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from topoembedx) (0.5.1)\n", - "Collecting numpy (from toponetx)\n", - " Using cached numpy-1.22.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (2.0 kB)\n", - "Requirement already satisfied: decorator==4.4.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from karateclub->topoembedx) (4.4.2)\n", - "Requirement already satisfied: python-louvain in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from karateclub->topoembedx) (0.16)\n", - "Requirement already satisfied: gensim>=4.0.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from karateclub->topoembedx) (4.3.3)\n", - "Requirement already satisfied: six in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from karateclub->topoembedx) (1.17.0)\n", - "Requirement already satisfied: python-Levenshtein in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from karateclub->topoembedx) (0.26.1)\n", - "Requirement already satisfied: python-dateutil>=2.7.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pandas->toponetx) (2.9.0.post0)\n", - "Requirement already satisfied: pytz>=2017.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pandas->toponetx) (2025.1)\n", - "Requirement already satisfied: contourpy>=1.0.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->topomodelx) (1.2.1)\n", - "Requirement already satisfied: cycler>=0.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->topomodelx) (0.12.1)\n", - "Requirement already satisfied: fonttools>=4.22.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->topomodelx) (4.56.0)\n", - "Requirement already satisfied: kiwisolver>=1.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->topomodelx) (1.4.8)\n", - "Requirement already satisfied: packaging>=20.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->topomodelx) (24.2)\n", - "Requirement already satisfied: pillow>=8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->topomodelx) (11.1.0)\n", - "Requirement already satisfied: pyparsing>=2.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->topomodelx) (3.2.1)\n", - "Requirement already satisfied: aiohttp in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyg-nightly->topomodelx) (3.11.12)\n", - "Requirement already satisfied: fsspec in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyg-nightly->topomodelx) (2025.2.0)\n", - "Requirement already satisfied: jinja2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyg-nightly->topomodelx) (3.1.5)\n", - "Requirement already satisfied: psutil>=5.8.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyg-nightly->topomodelx) (6.1.1)\n", - "Requirement already satisfied: art>=1.8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pyrandwalk->topoembedx) (6.4)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->toponetx) (3.4.1)\n", - "Requirement already satisfied: idna<4,>=2.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->toponetx) (3.10)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->toponetx) (2.3.0)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from requests->toponetx) (2025.1.31)\n", - "Requirement already satisfied: joblib>=1.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from scikit-learn->topomodelx) (1.4.2)\n", - "Requirement already satisfied: threadpoolctl>=3.1.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from scikit-learn->topomodelx) (3.5.0)\n", - "INFO: pip is looking at multiple versions of scipy to determine which version is compatible with other requirements. This could take a while.\n", - "Collecting scipy (from toponetx)\n", - " Using cached scipy-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\n", - " Using cached scipy-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\n", - " Using cached scipy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\n", - " Using cached scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (60 kB)\n", - " Using cached scipy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (60 kB)\n", - " Using cached scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (60 kB)\n", - "Requirement already satisfied: smart-open>=1.8.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from gensim>=4.0.0->karateclub->topoembedx) (7.1.0)\n", - "Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (2.4.6)\n", - "Requirement already satisfied: aiosignal>=1.1.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (1.3.2)\n", - "Requirement already satisfied: async-timeout<6.0,>=4.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (5.0.1)\n", - "Requirement already satisfied: attrs>=17.3.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (25.1.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (1.5.0)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (6.1.0)\n", - "Requirement already satisfied: propcache>=0.2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (0.2.1)\n", - "Requirement already satisfied: yarl<2.0,>=1.17.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from aiohttp->pyg-nightly->topomodelx) (1.18.3)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from jinja2->pyg-nightly->topomodelx) (3.0.2)\n", - "Requirement already satisfied: Levenshtein==0.26.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from python-Levenshtein->karateclub->topoembedx) (0.26.1)\n", - "Requirement already satisfied: rapidfuzz<4.0.0,>=3.9.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from Levenshtein==0.26.1->python-Levenshtein->karateclub->topoembedx) (3.12.1)\n", - "Requirement already satisfied: wrapt in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from smart-open>=1.8.1->gensim>=4.0.0->karateclub->topoembedx) (1.17.2)\n", - "Using cached numpy-1.22.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (16.8 MB)\n", - "Using cached scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (38.6 MB)\n", - "Installing collected packages: numpy, scipy\n", - " Attempting uninstall: numpy\n", - " Found existing installation: numpy 1.26.4\n", - " Uninstalling numpy-1.26.4:\n", - " Successfully uninstalled numpy-1.26.4\n", - " Attempting uninstall: scipy\n", - " Found existing installation: scipy 1.15.3\n", - " Uninstalling scipy-1.15.3:\n", - " Successfully uninstalled scipy-1.15.3\n", - "\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", - "ldpc 2.2.8 requires numpy>=1.24.0, but you have numpy 1.22.4 which is incompatible.\n", - "numba 0.61.2 requires numpy<2.3,>=1.24, but you have numpy 1.22.4 which is incompatible.\n", - "qldpc 0.2.5 requires numpy>=1.24.0, but you have numpy 1.22.4 which is incompatible.\n", - "qldpc 0.2.5 requires scipy>=1.14.1, but you have scipy 1.13.1 which is incompatible.\n", - "quits 0.0.1 requires networkx>=2.8.8, but you have networkx 2.6.3 which is incompatible.\n", - "quits 0.0.1 requires numpy>=1.24.0, but you have numpy 1.22.4 which is incompatible.\u001b[0m\u001b[31m\n", - "\u001b[0mSuccessfully installed numpy-1.22.4 scipy-1.13.1\n", - "Requirement already satisfied: qiskit in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (2.1.1)\n", - "Requirement already satisfied: stim in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (1.15.0)\n", - "Requirement already satisfied: sinter in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (1.15.0)\n", - "Requirement already satisfied: rustworkx>=0.15.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qiskit) (0.16.0)\n", - "Requirement already satisfied: numpy<3,>=1.17 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qiskit) (1.22.4)\n", - "Requirement already satisfied: scipy>=1.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qiskit) (1.13.1)\n", - "Requirement already satisfied: dill>=0.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qiskit) (0.3.9)\n", - "Requirement already satisfied: stevedore>=3.0.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qiskit) (5.4.1)\n", - "Requirement already satisfied: typing-extensions in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from qiskit) (4.12.2)\n", - "Requirement already satisfied: matplotlib in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from sinter) (3.8.4)\n", - "Requirement already satisfied: pbr>=2.0.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from stevedore>=3.0.0->qiskit) (6.1.1)\n", - "Requirement already satisfied: contourpy>=1.0.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (1.2.1)\n", - "Requirement already satisfied: cycler>=0.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (0.12.1)\n", - "Requirement already satisfied: fonttools>=4.22.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (4.56.0)\n", - "Requirement already satisfied: kiwisolver>=1.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (1.4.8)\n", - "Requirement already satisfied: packaging>=20.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (24.2)\n", - "Requirement already satisfied: pillow>=8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (11.1.0)\n", - "Requirement already satisfied: pyparsing>=2.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (3.2.1)\n", - "Requirement already satisfied: python-dateutil>=2.7 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->sinter) (2.9.0.post0)\n", - "Requirement already satisfied: setuptools in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pbr>=2.0.0->stevedore>=3.0.0->qiskit) (75.8.0)\n", - "Requirement already satisfied: six>=1.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib->sinter) (1.17.0)\n", - "Requirement already satisfied: ldpc in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (2.2.8)\n", - "Requirement already satisfied: bposd in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (2.1)\n", - "Collecting numpy>=1.24.0 (from ldpc)\n", - " Using cached numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (62 kB)\n", - "Requirement already satisfied: scipy>=1.9.3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc) (1.13.1)\n", - "Requirement already satisfied: tqdm in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc) (4.67.1)\n", - "Requirement already satisfied: pytest in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc) (8.3.5)\n", - "Requirement already satisfied: stim in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc) (1.15.0)\n", - "Requirement already satisfied: sinter in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc) (1.15.0)\n", - "Requirement already satisfied: pymatching in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from ldpc) (2.2.1)\n", - " Using cached numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\n", - "Requirement already satisfied: networkx in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymatching->ldpc) (2.6.3)\n", - "Requirement already satisfied: matplotlib in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymatching->ldpc) (3.8.4)\n", - "Requirement already satisfied: exceptiongroup>=1.0.0rc8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc) (1.2.2)\n", - "Requirement already satisfied: iniconfig in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc) (2.0.0)\n", - "Requirement already satisfied: packaging in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc) (24.2)\n", - "Requirement already satisfied: pluggy<2,>=1.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc) (1.5.0)\n", - "Requirement already satisfied: tomli>=1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pytest->ldpc) (2.2.1)\n", - "Requirement already satisfied: contourpy>=1.0.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching->ldpc) (1.2.1)\n", - "Requirement already satisfied: cycler>=0.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching->ldpc) (0.12.1)\n", - "Requirement already satisfied: fonttools>=4.22.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching->ldpc) (4.56.0)\n", - "Requirement already satisfied: kiwisolver>=1.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching->ldpc) (1.4.8)\n", - "Requirement already satisfied: pillow>=8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching->ldpc) (11.1.0)\n", - "Requirement already satisfied: pyparsing>=2.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching->ldpc) (3.2.1)\n", - "Requirement already satisfied: python-dateutil>=2.7 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib->pymatching->ldpc) (2.9.0.post0)\n", - "Requirement already satisfied: six>=1.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib->pymatching->ldpc) (1.17.0)\n", - "Using cached numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (18.2 MB)\n", - "Installing collected packages: numpy\n", - " Attempting uninstall: numpy\n", - " Found existing installation: numpy 1.22.4\n", - " Uninstalling numpy-1.22.4:\n", - " Successfully uninstalled numpy-1.22.4\n", - "\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", - "karateclub 1.3.3 requires numpy<1.23.0, but you have numpy 1.26.4 which is incompatible.\n", - "qldpc 0.2.5 requires scipy>=1.14.1, but you have scipy 1.13.1 which is incompatible.\n", - "quits 0.0.1 requires networkx>=2.8.8, but you have networkx 2.6.3 which is incompatible.\u001b[0m\u001b[31m\n", - "\u001b[0mSuccessfully installed numpy-1.26.4\n", - "Requirement already satisfied: pymoo in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (0.6.1.3)\n", - "Requirement already satisfied: numpy>=1.15 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (1.26.4)\n", - "Requirement already satisfied: scipy>=1.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (1.13.1)\n", - "Requirement already satisfied: matplotlib>=3 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (3.8.4)\n", - "Requirement already satisfied: autograd>=1.4 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (1.7.0)\n", - "Requirement already satisfied: cma==3.2.2 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (3.2.2)\n", - "Requirement already satisfied: alive-progress in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (3.2.0)\n", - "Requirement already satisfied: dill in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (0.3.9)\n", - "Requirement already satisfied: Deprecated in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from pymoo) (1.2.18)\n", - "Requirement already satisfied: contourpy>=1.0.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (1.2.1)\n", - "Requirement already satisfied: cycler>=0.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (0.12.1)\n", - "Requirement already satisfied: fonttools>=4.22.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (4.56.0)\n", - "Requirement already satisfied: kiwisolver>=1.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (1.4.8)\n", - "Requirement already satisfied: packaging>=20.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (24.2)\n", - "Requirement already satisfied: pillow>=8 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (11.1.0)\n", - "Requirement already satisfied: pyparsing>=2.3.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (3.2.1)\n", - "Requirement already satisfied: python-dateutil>=2.7 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from matplotlib>=3->pymoo) (2.9.0.post0)\n", - "Requirement already satisfied: about-time==4.2.1 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from alive-progress->pymoo) (4.2.1)\n", - "Requirement already satisfied: grapheme==0.6.0 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from alive-progress->pymoo) (0.6.0)\n", - "Requirement already satisfied: wrapt<2,>=1.10 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from Deprecated->pymoo) (1.17.2)\n", - "Requirement already satisfied: six>=1.5 in /home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib>=3->pymoo) (1.17.0)\n" - ] - } - ], + "outputs": [], "source": [ "\n", "!pip install torch==2.6.0 \n", @@ -491,7 +85,7 @@ }, { "cell_type": "markdown", - "id": "d2663042", + "id": "6", "metadata": {}, "source": [ "If you have installed all the packages, please try to run this in the new environment:" @@ -499,51 +93,23 @@ }, { "cell_type": "code", - "execution_count": 3, - "id": "fca2c653", + "execution_count": null, + "id": "7", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n" - ] - } - ], + "outputs": [], "source": [ - "import random\n", "import numpy as np\n", - "import torch\n", "from code_construction.code_construction import CodeConstructor\n", "from bayesian_optimization.objective_function import ObjectiveFunction\n", "from bayesian_optimization.encoder import *\n", "from bayesian_optimization.chaincomplexembedding import *\n", "from bayesian_optimization.gp import *\n", - "import gpytorch\n", - "from gpytorch.mlls import ExactMarginalLogLikelihood\n", - "from gpytorch.distributions import MultivariateNormal\n", - "from gpytorch.kernels import RBFKernel, MaternKernel, SpectralMixtureKernel, ScaleKernel\n", - "import copy\n", - "from typing import Dict, Optional, Tuple, Any, List\n", - "\n", - "import torch\n", - "import gpytorch\n", - "from gpytorch.mlls import ExactMarginalLogLikelihood\n", - "from torch.nn.utils import clip_grad_norm_\n", - "import math\n", - "from dataclasses import dataclass, asdict\n", - "from typing import Tuple, Union, Dict, Any\n", - "\n", - "from torch.distributions.normal import Normal\n", - "from bayesian_optimization.bo import BO_on_QEC\n", - "import pymoo" + "\n" ] }, { "cell_type": "markdown", - "id": "ef3afdda", + "id": "8", "metadata": {}, "source": [ "## The instructions of how to run the file" @@ -551,7 +117,7 @@ }, { "cell_type": "markdown", - "id": "603f22d4", + "id": "9", "metadata": {}, "source": [ "Type following command in the terminal:\n", @@ -563,7 +129,7 @@ }, { "cell_type": "markdown", - "id": "d7abff86", + "id": "10", "metadata": {}, "source": [ "## Read & plot the results" @@ -571,7 +137,7 @@ }, { "cell_type": "markdown", - "id": "8a36536d", + "id": "11", "metadata": {}, "source": [ "#### Reading the results of BO/EA/RS:\n" @@ -580,12 +146,11 @@ { "cell_type": "code", "execution_count": null, - "id": "47c00e9f", + "id": "12", "metadata": {}, "outputs": [], "source": [ "import pickle\n", - "from code_construction.code_construction import CodeConstructor\n", "l=12\n", "g=6\n", "dataset_index = 0\n", @@ -608,7 +173,7 @@ }, { "cell_type": "markdown", - "id": "f6ae4cf9", + "id": "13", "metadata": {}, "source": [ "#### The data of figure 7; figure 8; figure 9" @@ -616,7 +181,7 @@ }, { "cell_type": "markdown", - "id": "d1245e2a", + "id": "14", "metadata": {}, "source": [ "Figure 7\n", @@ -631,7 +196,7 @@ { "cell_type": "code", "execution_count": null, - "id": "77d96691", + "id": "15", "metadata": {}, "outputs": [], "source": [ @@ -644,7 +209,7 @@ }, { "cell_type": "markdown", - "id": "1354068b", + "id": "16", "metadata": {}, "source": [ "Figure 8\n", @@ -659,20 +224,9 @@ { "cell_type": "code", "execution_count": null, - "id": "3d4c37a4", + "id": "17", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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/9L5dp9z4Arvw5lqLK9aLnfF5axK32F3zJgxVpcgUmb081yxdL7d5xy4wvtwphqtrsXtbhs5E62zJCIiUvYq/ZWX8uTr68vzzz+Pr6+v1aWIiIi4DV15EREREZeiKy8iIiLiUhReRERExKUovIiIiIhLUXgRkRI5dOgQ3bt3p1mzZrRq1YqvvvrK6pLcls61zkFFcsVzrQ67IlIix44dIzU1lfj4eFJSUmjTpg27du0iICDA6tLcjs61zkFFcsVzrSsvTsAVU69UPrVq1SI+Ph6AmjVrEhYWxqlTp6wtyk3pXOscVCRXPNcKL07Ay8uLSZMmsW3bNubPn8/jjz9Odna21WVJGXnllVdo164dgYGBhIeH069fP3bu3Fmmx1iyZAl9+/YlMjISm83G3LlzL7hfQkICMTEx+Pn50aFDB1avXn1Fx0tKSsJutzvdAqjvvvsurVq1Kl6So1OnTvz0009legxXOtf/+te/sNlsPP7441d07ItxpXNQ3o4cOcI999xD9erVqVKlCi1btmTt2rVl9vo61xem8OIEXDH1SsktXryYMWPG8Ntvv/HLL79QWFjIDTfccNGAunz5cgoLC8/bvm3bNlJTUy/4nOzsbOLi4khISLhoHTNmzGDs2LE8//zzrFu3jri4OPr06UNaWlrxPvHx8bRo0eK8r6NHjxbvc+rUKYYNG8YHH3xQ0lNQYerUqcO//vUvkpKSWLt2LT179uS2225j69atF9zfnc/1mjVreP/992nVqtUl93Pnc1DeTp8+TefOnfH29uann35i27ZtTJgwgWrVql1wf53rMmTIZS1evNi45ZZbjFq1ahmAMWfOnPP2efvtt426desavr6+Rvv27Y1Vq1Zd0bHWrl1rNG/e/CorFmeWlpZmAMbixYvPe8xutxtxcXHGgAEDjKKiouLtO3bsMCIiIox///vfl339i/2Mtm/f3hgzZsw5x4qMjDReeeWVEteel5dndO3a1ZgyZUqJn2O1atWqGR999NF52935XGdlZRkNGzY0fvnlF+O6664zHnvssQvu587noCL85S9/Mbp06VKifXWuy5auvJTA5ZJvpUy9csUyMjIACA0NPe8xDw8PfvzxR9avX8+wYcNwOBzs3buXnj170q9fP5555pkrOmZBQQFJSUn07t37nGP17t2blStXlug1DMNgxIgR9OzZk6FDh15RHRXJbrfz5Zdfkp2dTadOnc573J3P9ZgxY7j55pvPqeFC3PkcVIRvv/2Wtm3bMnDgQMLDw2ndujUffvjhBffVuS5j1mYn18MFkm9lTL1yZex2u3HzzTcbnTt3vuR+Bw4cMKKjo43Bgwcb0dHRxrBhwwyHw1GiY1zoZ/TIkSMGYKxYseKc7U8//bTRvn37Er3u0qVLDZvNZsTFxRV/bdq0qUTPrUibNm0yAgICDE9PTyM4ONj44YcfLrm/u53rL774wmjRooWRm5trGIZxySsvZ7nbOagovr6+hq+vrzFu3Dhj3bp1xvvvv2/4+fkZkydPvuhzdK7LhpdlqclNnE2948aNK95WKVKvXJExY8awZcsWli1bdsn9oqOjmTp1Ktdddx316tXj448/xmazVVCVF9alSxccDoelNZRE48aN2bBhAxkZGcyaNYvhw4ezePFimjVrdsH93elcHzp0iMcee4xffvkFPz+/Ej/Pnc5BRXI4HLRt25Z//vOfALRu3ZotW7bw3nvvMXz48As+R+e6bOi20VU6ceIEdrudiIiIc7ZHRESQkpJSotdYvnw5M2bMYO7cucTHxxMfH8/mzZvLo1yx0COPPML333/PwoULqVOnziX3TU1NZfTo0fTt25ecnByeeOKJqzp2WFgYnp6e53UKTE1NpWbNmlf12s7Gx8eHBg0a0KZNG1555RXi4uJ44403Lrq/O53rpKQk0tLSuOaaa/Dy8sLLy4vFixfz5ptv4uXlhd1uv+Dz3OkcVKRatWqdF4qbNm3KwYMHL/ocneuyofDiBM6m3g0bNhR/tWzZ0uqypIwYhsEjjzzCnDlzWLBgAbGxsZfc/8SJE/Tq1YumTZsye/ZsEhMTmTFjBk899dQV1+Dj40ObNm1ITEws3uZwOEhMTLxgfxB34nA4yM/Pv+Bj7naue/XqxebNm8/5W9K2bVuGDBnChg0b8PT0PO857nYOKlLnzp3Pm/Zg165d1K1b94L761yXIavvW7ka/nDPMT8/3/D09DzvPuSwYcOMW2+9tWKLE6f00EMPGcHBwcaiRYuMY8eOFX/l5OSct6/dbjfatm1r3HTTTUZ+fn7x9g0bNhihoaHGxIkTL3iMrKwsY/369cb69esNwJg4caKxfv1648CBA8X7fPnll4avr68xefJkY9u2bcbo0aONkJAQIyUlpezftEWeffZZY/HixUZycrKxadMm49lnnzVsNpsxf/788/atLOf6cqONKsM5KC+rV682vLy8jH/84x/G7t27jc8//9zw9/c3pk2bdt6+OtdlS+GllP4YXgzD7LD7yCOPFLftdrtRu3btUnXYFfcFXPDr008/veD+8+fPL+5s+b/WrVtnHDp06ILPWbhw4QWPMXz48HP2e+utt4zo6GjDx8fHaN++vfHbb79d7dtzKvfee69Rt25dw8fHx6hRo4bRq1evCwaXsyrDub5ch93KcA7K03fffWe0aNHC8PX1NZo0aWJ88MEHF91X57rsaG2jEjhz5gx79uwBzA5ZEydOpEePHoSGhhIdHc2MGTMYPnw477//Pu3bt2fSpEnMnDmTHTt2nNcXRkRERK6OwksJLFq0iB49epy3ffjw4UyePBmAt99+m1dffZWUlBTi4+N588036dChQwVXKiIi4v4UXkRERMSlaLSRiIiIuBSFFxEREXEpCi8iIiLiUhReRERExKUovIiIiIhLUXgRERERl6LwIiIiIi5F4UVERERcisKLiIiIuBSFFxEREXEpCi8iIiLiUhReRERExKX8P/huhP4Z0ROSAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "pp_list = [0.07, 0.05605161820417657, 0.04488262719009683, 0.035939198332281444, 0.028777860335503124, 0.02304350914655339, 0.018451799668100875, 0.014775046145376319, 0.011830932078424629, 0.00947346982656289]\n", "gross = [0.01468388594008141, 0.00730116936108316, 0.003624729974722629, 0.001908233950801752, 0.0010434677566117934, 0.0005491556129465502, 0.00029966005248871497, 0.00017099405602716278, 6.835902874036126e-05, 4.584489121428614e-05]\n", @@ -689,7 +243,7 @@ }, { "cell_type": "markdown", - "id": "f1293b7b", + "id": "18", "metadata": {}, "source": [ "Figure 9\n", @@ -702,7 +256,7 @@ { "cell_type": "code", "execution_count": null, - "id": "15bf1845", + "id": "19", "metadata": {}, "outputs": [], "source": [ @@ -710,7 +264,6 @@ "g = 6\n", "para_dict = {'l':l,'g':g}\n", "code_class = 'bb'\n", - "import numpy as np\n", "from code_construction.code_construction import CodeConstructor\n", "code_constructor = CodeConstructor(method=code_class,para_dict = para_dict)\n", "x = [0., 1., 1., 0., 1., 1., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1., 1., 1.,\n", @@ -727,7 +280,7 @@ { "cell_type": "code", "execution_count": null, - "id": "21f12a88", + "id": "20", "metadata": {}, "outputs": [], "source": [ @@ -753,43 +306,12 @@ { "cell_type": "code", "execution_count": null, - "id": "cca8ae5d", + "id": "21", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "n=144 non-degenerate CSS Hamming bound: code rates:[0.7847222222222222, 0.7430555555555556, 0.7013888888888888, 0.6597222222222222, 0.6180555555555556, 0.5833333333333334, 0.5416666666666666, 0.5069444444444444, 0.4722222222222222, 0.4375, 0.4027777777777778, 0.3680555555555556, 0.3402777777777778, 0.3055555555555556, 0.2777777777777778, 0.25, 0.2152777777777778, 0.1875, 0.1597222222222222, 0.13194444444444445, 0.10416666666666667, 0.0763888888888889, 0.05555555555555555, 0.027777777777777776, 0.0], psuedo t//n:[0.034722222222222224, 0.041666666666666664, 0.04861111111111111, 0.05555555555555555, 0.0625, 0.06944444444444445, 0.0763888888888889, 0.08333333333333333, 0.09027777777777778, 0.09722222222222222, 0.10416666666666667, 0.1111111111111111, 0.11805555555555555, 0.125, 0.13194444444444445, 0.1388888888888889, 0.14583333333333334, 0.1527777777777778, 0.1597222222222222, 0.16666666666666666, 0.1736111111111111, 0.18055555555555555, 0.1875, 0.19444444444444445, 0.2013888888888889]\n", - "n=72 non-degenerate CSS Hamming bound: code rates:[0.7916666666666666, 0.7083333333333334, 0.625, 0.5555555555555556, 0.4861111111111111, 0.4166666666666667, 0.3472222222222222, 0.2916666666666667, 0.2361111111111111, 0.18055555555555555, 0.125, 0.06944444444444445, 0.013888888888888888], psuedo t//n:[0.041666666666666664, 0.05555555555555555, 0.06944444444444445, 0.08333333333333333, 0.09722222222222222, 0.1111111111111111, 0.125, 0.1388888888888889, 0.1527777777777778, 0.16666666666666666, 0.18055555555555555, 0.19444444444444445, 0.20833333333333334]\n", - "n=36 non-degenerate CSS Hamming bound: code rates:[0.6388888888888888, 0.5, 0.3611111111111111, 0.25, 0.1388888888888889, 0.027777777777777776], psuedo t//n:[0.08333333333333333, 0.1111111111111111, 0.1388888888888889, 0.16666666666666666, 0.19444444444444445, 0.2222222222222222]\n", - "n=288 non-degenerate CSS Hamming bound: code rates:[0.78125, 0.7569444444444444, 0.7361111111111112, 0.7152777777777778, 0.6944444444444444, 0.6736111111111112, 0.6527777777777778, 0.6319444444444444, 0.6111111111111112, 0.59375, 0.5729166666666666, 0.5555555555555556, 0.5347222222222222, 0.5173611111111112, 0.5, 0.4826388888888889, 0.4652777777777778, 0.4479166666666667, 0.4305555555555556, 0.4131944444444444, 0.3958333333333333, 0.3784722222222222, 0.3645833333333333, 0.3472222222222222, 0.3333333333333333, 0.3159722222222222, 0.2986111111111111, 0.2847222222222222, 0.2708333333333333, 0.2534722222222222, 0.23958333333333334, 0.22569444444444445, 0.20833333333333334, 0.19444444444444445, 0.18055555555555555, 0.16666666666666666, 0.1527777777777778, 0.1388888888888889, 0.125, 0.1111111111111111, 0.09722222222222222, 0.08333333333333333, 0.06944444444444445, 0.059027777777777776, 0.04513888888888889, 0.03125, 0.017361111111111112, 0.006944444444444444], psuedo t//n:[0.03125, 0.034722222222222224, 0.03819444444444445, 0.041666666666666664, 0.04513888888888889, 0.04861111111111111, 0.052083333333333336, 0.05555555555555555, 0.059027777777777776, 0.0625, 0.06597222222222222, 0.06944444444444445, 0.07291666666666667, 0.0763888888888889, 0.0798611111111111, 0.08333333333333333, 0.08680555555555555, 0.09027777777777778, 0.09375, 0.09722222222222222, 0.10069444444444445, 0.10416666666666667, 0.1076388888888889, 0.1111111111111111, 0.11458333333333333, 0.11805555555555555, 0.12152777777777778, 0.125, 0.1284722222222222, 0.13194444444444445, 0.13541666666666666, 0.1388888888888889, 0.1423611111111111, 0.14583333333333334, 0.14930555555555555, 0.1527777777777778, 0.15625, 0.1597222222222222, 0.16319444444444445, 0.16666666666666666, 0.1701388888888889, 0.1736111111111111, 0.17708333333333334, 0.18055555555555555, 0.1840277777777778, 0.1875, 0.1909722222222222, 0.19444444444444445]\n", - "n=576 non-degenerate CSS Hamming bound: code rates:[0.7899305555555556, 0.7777777777777778, 0.765625, 0.7552083333333334, 0.7430555555555556, 0.7326388888888888, 0.7222222222222222, 0.7100694444444444, 0.6996527777777778, 0.6892361111111112, 0.6788194444444444, 0.6684027777777778, 0.6579861111111112, 0.6475694444444444, 0.6371527777777778, 0.6284722222222222, 0.6180555555555556, 0.6076388888888888, 0.5989583333333334, 0.5885416666666666, 0.578125, 0.5694444444444444, 0.5607638888888888, 0.5503472222222222, 0.5416666666666666, 0.53125, 0.5225694444444444, 0.5138888888888888, 0.5052083333333334, 0.4947916666666667, 0.4861111111111111, 0.4774305555555556, 0.46875, 0.4600694444444444, 0.4513888888888889, 0.4427083333333333, 0.4340277777777778, 0.4253472222222222, 0.4166666666666667, 0.4079861111111111, 0.4010416666666667, 0.3923611111111111, 0.3836805555555556, 0.375, 0.3680555555555556, 0.359375, 0.3506944444444444, 0.34375, 0.3350694444444444, 0.3263888888888889, 0.3194444444444444, 0.3107638888888889, 0.3038194444444444, 0.2951388888888889, 0.2881944444444444, 0.2795138888888889, 0.2725694444444444, 0.265625, 0.2569444444444444, 0.25, 0.24131944444444445, 0.234375, 0.22743055555555555, 0.2204861111111111, 0.21180555555555555, 0.2048611111111111, 0.19791666666666666, 0.1909722222222222, 0.1840277777777778, 0.1753472222222222, 0.1684027777777778, 0.16145833333333334, 0.1545138888888889, 0.14756944444444445, 0.140625, 0.13368055555555555, 0.1267361111111111, 0.11979166666666667, 0.11284722222222222, 0.10590277777777778, 0.09895833333333333, 0.0920138888888889, 0.08506944444444445, 0.0798611111111111, 0.07291666666666667, 0.06597222222222222, 0.059027777777777776, 0.052083333333333336, 0.04513888888888889, 0.03993055555555555, 0.03298611111111111, 0.026041666666666668, 0.019097222222222224, 0.013888888888888888, 0.006944444444444444, 0.0], psuedo t//n:[0.027777777777777776, 0.029513888888888888, 0.03125, 0.03298611111111111, 0.034722222222222224, 0.036458333333333336, 0.03819444444444445, 0.03993055555555555, 0.041666666666666664, 0.043402777777777776, 0.04513888888888889, 0.046875, 0.04861111111111111, 0.050347222222222224, 0.052083333333333336, 0.05381944444444445, 0.05555555555555555, 0.057291666666666664, 0.059027777777777776, 0.06076388888888889, 0.0625, 0.0642361111111111, 0.06597222222222222, 0.06770833333333333, 0.06944444444444445, 0.07118055555555555, 0.07291666666666667, 0.07465277777777778, 0.0763888888888889, 0.078125, 0.0798611111111111, 0.08159722222222222, 0.08333333333333333, 0.08506944444444445, 0.08680555555555555, 0.08854166666666667, 0.09027777777777778, 0.0920138888888889, 0.09375, 0.0954861111111111, 0.09722222222222222, 0.09895833333333333, 0.10069444444444445, 0.10243055555555555, 0.10416666666666667, 0.10590277777777778, 0.1076388888888889, 0.109375, 0.1111111111111111, 0.11284722222222222, 0.11458333333333333, 0.11631944444444445, 0.11805555555555555, 0.11979166666666667, 0.12152777777777778, 0.1232638888888889, 0.125, 0.1267361111111111, 0.1284722222222222, 0.13020833333333334, 0.13194444444444445, 0.13368055555555555, 0.13541666666666666, 0.1371527777777778, 0.1388888888888889, 0.140625, 0.1423611111111111, 0.1440972222222222, 0.14583333333333334, 0.14756944444444445, 0.14930555555555555, 0.15104166666666666, 0.1527777777777778, 0.1545138888888889, 0.15625, 0.1579861111111111, 0.1597222222222222, 0.16145833333333334, 0.16319444444444445, 0.16493055555555555, 0.16666666666666666, 0.1684027777777778, 0.1701388888888889, 0.171875, 0.1736111111111111, 0.1753472222222222, 0.17708333333333334, 0.17881944444444445, 0.18055555555555555, 0.18229166666666666, 0.1840277777777778, 0.1857638888888889, 0.1875, 0.1892361111111111, 0.1909722222222222, 0.19270833333333334]\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_340170/1970098735.py:13: RuntimeWarning: overflow encountered in multiply\n", - " s += comb(n, i)* (3**i)\n" - ] - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# This is used to plot the non-degenerate Hamming bound in the psuedo_t vs code rate view.\n", "from scipy.special import comb\n", - "import numpy as np\n", "import matplotlib.pyplot as plt\n", "def binomial_probability(n,d_e,p_p):\n", " return comb(n, d_e) * (p_p ** d_e) * ((1 - p_p) ** (n - d_e))\n", @@ -823,7 +345,7 @@ ], "metadata": { "kernelspec": { - "display_name": "QEC2", + "display_name": "qec", "language": "python", "name": "python3" }, @@ -837,7 +359,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.16" + "version": "3.10.19" } }, "nbformat": 4, diff --git a/requirements.txt b/requirements.txt index 52c077c..a561bf0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,192 +1,19 @@ -about-time==4.2.1 -aiocache==0.12.3 -aiofiles==24.1.0 -aiohappyeyeballs==2.4.6 -aiohttp==3.11.12 -aiopubsub==3.0.0 -aiosignal==1.3.2 -aiosmtplib==4.0.0 -alembic==1.14.1 -alive-progress==3.2.0 -aniso8601==7.0.0 -APScheduler==3.11.0 -art==6.4 -asttokens @ file:///home/conda/feedstock_root/build_artifacts/asttokens_1733250440834/work -async-timeout==5.0.1 -attrs==25.1.0 -autograd==1.7.0 -ax==0.36.0 -botorch==0.13.0 +botorch==0.16.1 bposd==2.1 -certifi @ file:///croot/certifi_1738623731865/work/certifi -charset-normalizer==3.4.1 -cma==3.2.2 -comm @ file:///home/conda/feedstock_root/build_artifacts/comm_1733502965406/work -contourpy==1.2.1 -cssselect==1.2.0 -cycler==0.12.1 -Cython==3.0.12 -debugpy @ file:///home/conda/feedstock_root/build_artifacts/debugpy_1737269734548/work -decorator==4.4.2 -Deprecated==1.2.18 -dill==0.3.9 -docopt==0.6.2 -dotmap==1.3.30 -et_xmlfile==2.0.0 -exceptiongroup @ file:///home/conda/feedstock_root/build_artifacts/exceptiongroup_1733208806608/work -executing @ file:///home/conda/feedstock_root/build_artifacts/executing_1733569351617/work -filelock==3.17.0 -fonttools==4.56.0 -frozenlist==1.5.0 -fsspec==2025.2.0 -future==1.0.0 -gensim==4.3.3 -gpytorch==1.14 -GraKeL==0.1.10 -grapheme==0.6.0 -graphene==2.1.9 -graphene-sqlalchemy==2.3.0 -graphql-core==2.3.2 -graphql-relay==2.0.1 -graphql-server-core==2.0.0 -graphql-ws==0.4.4 -greenlet==3.1.1 -gudhi==3.10.1 -gunicorn==23.0.0 -html5tagger==1.3.0 -httptools==0.6.4 -idna==3.10 -importlib_metadata @ file:///home/conda/feedstock_root/build_artifacts/importlib-metadata_1737420181517/work -iniconfig==2.0.0 -ipykernel @ file:///home/conda/feedstock_root/build_artifacts/ipykernel_1719845459717/work -ipython @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_ipython_1738421264/work -jaxtyping==0.2.37 -jedi @ file:///home/conda/feedstock_root/build_artifacts/jedi_1733300866624/work -Jinja2==3.1.5 -joblib==1.4.2 -jupyter_client @ file:///home/conda/feedstock_root/build_artifacts/jupyter_client_1733440914442/work -jupyter_core @ file:///home/conda/feedstock_root/build_artifacts/jupyter_core_1727163409502/work -karateclub==1.3.3 -kiwisolver==1.4.8 -ldpc==2.2.8 -Levenshtein==0.26.1 -linear-operator==0.6 -loguru==0.7.3 -lxml==5.3.1 -Mako==1.3.9 -markdown2==2.5.3 -MarkupSafe==3.0.2 +Evaluate==0.4.6 +gpytorch==1.15.1 +ldpc==2.4.1 matplotlib==3.8.4 -matplotlib-inline @ file:///home/conda/feedstock_root/build_artifacts/matplotlib-inline_1733416936468/work -mpmath==1.3.0 -multidict==6.1.0 -multipledispatch==1.0.0 -mypy-extensions==1.0.0 -nest_asyncio @ file:///home/conda/feedstock_root/build_artifacts/nest-asyncio_1733325553580/work -networkx==2.6.3 -numpy==1.26.4 -nvidia-cublas-cu12==12.4.5.8 -nvidia-cuda-cupti-cu12==12.4.127 -nvidia-cuda-nvrtc-cu12==12.4.127 -nvidia-cuda-runtime-cu12==12.4.127 -nvidia-cudnn-cu12==9.1.0.70 -nvidia-cufft-cu12==11.2.1.3 -nvidia-curand-cu12==10.3.5.147 -nvidia-cusolver-cu12==11.6.1.9 -nvidia-cusparse-cu12==12.3.1.170 -nvidia-cusparselt-cu12==0.6.2 -nvidia-nccl-cu12==2.21.5 -nvidia-nvjitlink-cu12==12.4.127 -nvidia-nvtx-cu12==12.4.127 -openpyxl==3.1.5 -opt_einsum==3.4.0 -packaging @ file:///home/conda/feedstock_root/build_artifacts/packaging_1733203243479/work -pandas==1.3.5 -parso @ file:///home/conda/feedstock_root/build_artifacts/parso_1733271261340/work -passlib==1.7.4 -pexpect @ file:///home/conda/feedstock_root/build_artifacts/pexpect_1733301927746/work -pickleshare @ file:///home/conda/feedstock_root/build_artifacts/pickleshare_1733327343728/work -pillow==11.1.0 -platformdirs @ file:///home/conda/feedstock_root/build_artifacts/platformdirs_1733232627818/work -pluggy==1.5.0 -promise==2.3 -prompt_toolkit @ file:///home/conda/feedstock_root/build_artifacts/prompt-toolkit_1737453357274/work -propcache==0.2.1 -psutil @ file:///home/conda/feedstock_root/build_artifacts/psutil_1735327341346/work -psycopg2==2.9.10 -ptyprocess @ file:///home/conda/feedstock_root/build_artifacts/ptyprocess_1733302279685/work/dist/ptyprocess-0.7.0-py2.py3-none-any.whl#sha256=92c32ff62b5fd8cf325bec5ab90d7be3d2a8ca8c8a3813ff487a8d2002630d1f -pure_eval @ file:///home/conda/feedstock_root/build_artifacts/pure_eval_1733569405015/work -pyarrow==19.0.0 -pyasn1==0.6.1 -pyg-nightly==2.7.0.dev20250213 -Pygments @ file:///home/conda/feedstock_root/build_artifacts/pygments_1736243443484/work -PyGSP==0.5.1 -PyJWT==2.10.1 -PyMatching==2.2.1 -pymoo==0.6.1.3 -pyparsing==3.2.1 -pyquery==2.0.1 -pyrandwalk==1.1 -pyre-extensions==0.0.32 -pyro-api==0.1.2 -pyro-ppl==1.9.1 -pytest==8.3.5 -pytest-runner==6.0.1 -python-dateutil @ file:///home/conda/feedstock_root/build_artifacts/python-dateutil_1733215673016/work -python-Levenshtein==0.26.1 -python-louvain==0.16 -pytz==2025.1 -PyYAML==6.0.2 -pyzmq @ file:///home/conda/feedstock_root/build_artifacts/pyzmq_1738270958168/work -RapidFuzz==3.12.1 -redis==5.2.1 -requests==2.32.3 -rsa==4.9 -ruamel.yaml==0.18.10 -ruamel.yaml.clib==0.2.12 -Rx==1.6.3 -sanic==24.12.0 -Sanic-Cors==2.2.0 -Sanic-GraphQL==1.1.0 -sanic-jwt==1.8.0 -sanic-routing==23.12.0 -sanic_compress==0.1.1 -scikit-learn==1.6.1 -scipy==1.13.1 -singledispatch==3.7.0 -sinter==1.14.0 -six @ file:///home/conda/feedstock_root/build_artifacts/six_1733380938961/work -smart-open==7.1.0 -SQLAlchemy==1.4.54 -SQLAlchemy-Utils==0.41.2 -stack_data @ file:///home/conda/feedstock_root/build_artifacts/stack_data_1733569443808/work -stim==1.14.0 -stripe==11.6.0 -sympy==1.13.1 -threadpoolctl==3.5.0 -tomli==2.2.1 -TopoEmbedX==0.0.2.dev131 +numpy==2.4.2 +pandas==3.0.0 +pyldpc==0.7.9 +pymoo==0.6.1.6 +scikit_learn==1.8.0 +scipy==1.17.0 +sinter==1.15.0 +stim==1.16.dev1770241571 TopoModelX==0.0.1 TopoNetX==0.2.0 -torch==2.6.0 -torch-geometric==2.6.1 -tornado @ file:///home/conda/feedstock_root/build_artifacts/tornado_1732615898999/work -tqdm==4.67.1 -tracerite==1.1.1 -traitlets @ file:///home/conda/feedstock_root/build_artifacts/traitlets_1733367359838/work -trimesh==4.6.2 -triton==3.2.0 -typing==3.7.4.3 -typing-inspect==0.9.0 -typing_extensions @ file:///home/conda/feedstock_root/build_artifacts/typing_extensions_1733188668063/work -tzdata==2025.1 -tzlocal==5.3 -ujson==5.10.0 -urllib3==2.3.0 -uvloop==0.21.0 -wadler_lindig==0.1.3 -wcwidth @ file:///home/conda/feedstock_root/build_artifacts/wcwidth_1733231326287/work -websockets==15.0 -wrapt==1.17.2 -yarl==1.18.3 -zipp @ file:///home/conda/feedstock_root/build_artifacts/zipp_1732827521216/work +torch==2.5.1 #+cu121 +torch_geometric==2.7.0 +tqdm==4.67.3 diff --git a/test_bo.ipynb b/test_bo.ipynb index 08f410e..a538f82 100644 --- a/test_bo.ipynb +++ b/test_bo.ipynb @@ -2,8 +2,8 @@ "cells": [ { "cell_type": "code", - "execution_count": 26, - "id": "4a6bebbf", + "execution_count": null, + "id": "0", "metadata": {}, "outputs": [], "source": [ @@ -13,7 +13,7 @@ }, { "cell_type": "markdown", - "id": "cbc45bb1", + "id": "1", "metadata": {}, "source": [ "# Define the search space" @@ -21,8 +21,8 @@ }, { "cell_type": "code", - "execution_count": 1, - "id": "4fd1756b", + "execution_count": null, + "id": "2", "metadata": {}, "outputs": [], "source": [ @@ -37,7 +37,7 @@ }, { "cell_type": "markdown", - "id": "9923d5a0", + "id": "3", "metadata": {}, "source": [ "# Define the objective function\n" @@ -46,7 +46,7 @@ { "cell_type": "code", "execution_count": null, - "id": "5ade0e42", + "id": "4", "metadata": {}, "outputs": [], "source": [ @@ -78,13 +78,12 @@ "# pl_to_obj = Obj_Func.nllerpq_with_std\n", "# if we use forward function\n", "# obj_func = Obj_Func.forward\n", - "# pl_to_obj = Obj_Func.pl_to_obj_with_std\n", - "\n" + "# pl_to_obj = Obj_Func.pl_to_obj_with_std" ] }, { "cell_type": "markdown", - "id": "3279d950", + "id": "5", "metadata": {}, "source": [ "# Initialize the Get_new_points_function " @@ -92,13 +91,14 @@ }, { "cell_type": "code", - "execution_count": 29, - "id": "8382b866", + "execution_count": null, + "id": "6", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import torch\n", + "\n", "class Get_new_points_function():\n", " def __init__(self,method='qc-ldpc-hgp',hyperparameters = {'p': 2, 'q': 6, 'm': 2},encode='None'):\n", " self.method = method\n", @@ -149,23 +149,10 @@ }, { "cell_type": "code", - "execution_count": 30, - "id": "af2fe6ca", + "execution_count": null, + "id": "7", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "tensor([0.5301, 0.1433, 0.4758, 0.1553, 0.1842, 0.2261, 0.1476, 0.7077, 0.2567,\n", - " 0.4345, 0.3065, 0.2032, 0.6829, 0.2447, 0.2757, 0.2973, 0.4243, 0.2770,\n", - " 0.2961, 0.2400], device='cuda:0')" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "init_num = 20\n", "X_init = gnp(init_num)\n", @@ -186,7 +173,7 @@ }, { "cell_type": "markdown", - "id": "6b85278e", + "id": "8", "metadata": {}, "source": [ "# Define the Gaussian Process and it's training process" @@ -194,8 +181,8 @@ }, { "cell_type": "code", - "execution_count": 31, - "id": "c57f1075", + "execution_count": null, + "id": "9", "metadata": {}, "outputs": [], "source": [ @@ -203,8 +190,6 @@ "from bayesian_optimization.chaincomplexembedding import *\n", "from bayesian_optimization.gp import *\n", "import gpytorch\n", - "from gpytorch.mlls import ExactMarginalLogLikelihood\n", - "from gpytorch.distributions import MultivariateNormal\n", "from gpytorch.kernels import RBFKernel, MaternKernel, SpectralMixtureKernel, ScaleKernel\n", "\n", "class E():\n", @@ -298,7 +283,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b87b3643", + "id": "10", "metadata": {}, "outputs": [], "source": [ @@ -307,7 +292,6 @@ "from typing import Dict, Optional, Tuple, Any, List\n", "\n", "import torch\n", - "import gpytorch\n", "from gpytorch.mlls import ExactMarginalLogLikelihood\n", "from torch.nn.utils import clip_grad_norm_\n", "\n", @@ -645,7 +629,7 @@ { "cell_type": "code", "execution_count": null, - "id": "05472b9e", + "id": "11", "metadata": {}, "outputs": [], "source": [ @@ -686,7 +670,7 @@ }, { "cell_type": "markdown", - "id": "4474c609", + "id": "12", "metadata": {}, "source": [ "# Acquisition function and it's optimization" @@ -695,16 +679,14 @@ { "cell_type": "code", "execution_count": null, - "id": "06762aea", + "id": "13", "metadata": {}, "outputs": [], "source": [ "# log_std_normalizer.py\n", - "import math\n", "from dataclasses import dataclass, asdict\n", "from typing import Tuple, Union, Dict, Any\n", "\n", - "import numpy as np\n", "import torch\n", "\n", "TensorLike = Union[torch.Tensor, np.ndarray, float]\n", @@ -863,7 +845,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6478f2d7", + "id": "14", "metadata": {}, "outputs": [], "source": [ @@ -1023,7 +1005,7 @@ { "cell_type": "code", "execution_count": null, - "id": "391de9c1", + "id": "15", "metadata": {}, "outputs": [], "source": [ @@ -1042,7 +1024,7 @@ }, { "cell_type": "markdown", - "id": "1761403f", + "id": "16", "metadata": {}, "source": [ "params for hill climbing:" @@ -1051,7 +1033,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c4d61c9c", + "id": "17", "metadata": {}, "outputs": [], "source": [ @@ -1128,7 +1110,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b6e29b61", + "id": "18", "metadata": {}, "outputs": [], "source": [ @@ -1147,7 +1129,7 @@ }, { "cell_type": "markdown", - "id": "ba52d0d9", + "id": "19", "metadata": {}, "source": [ "# Assemble the BO\n" @@ -1156,908 +1138,9 @@ { "cell_type": "code", "execution_count": null, - "id": "b06101f5", + "id": "20", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Initial best value: -0.1862\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/optim/lr_scheduler.py:62: UserWarning: The verbose parameter is deprecated. Please use get_last_lr() to access the learning rate.\n", - " warnings.warn(\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[round 0 | 8/80] nll=3.6365 len=[[0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996]] out=6.965e-01 noise=6.849e-01\n", - "[round 0 | 16/80] nll=3.5021 len=[[0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996]] out=6.988e-01 noise=6.278e-01\n", - "[round 0 | 24/80] nll=3.5551 len=[[0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996, 0.6931471824645996]] out=6.997e-01 noise=5.885e-01\n", - "[round 0] early stop @ 29, best nll=3.4734\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "BB-BO (GP+EI+HC): 0%| | 0/50 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# Flatten the provided evaluation history into a 1D vector and plot it.\n", "import matplotlib.pyplot as plt\n", - "import pandas as pd\n", " \n", "\n", "# Flatten to 1D list\n", @@ -2155,7 +1216,7 @@ ], "metadata": { "kernelspec": { - "display_name": "QEC2", + "display_name": "qec", "language": "python", "name": "python3" }, @@ -2169,7 +1230,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.13" + "version": "3.10.19" } }, "nbformat": 4, diff --git a/test_gp.ipynb b/test_gp.ipynb index c66631c..3cadd57 100644 --- a/test_gp.ipynb +++ b/test_gp.ipynb @@ -2,31 +2,17 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, - "id": "471440b1", + "execution_count": null, + "id": "0", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/cyyh/miniconda3/envs/QEC2/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n" - ] - } - ], + "outputs": [], "source": [ "import numpy as np\n", - "from bayesian_optimization.encoder import *\n", + "from bayesian_optimization.encoder import CSSEncoder\n", + "from gpytorch.mlls import ExactMarginalLogLikelihood\n", "from code_construction.code_construction import *\n", "from dataset import *\n", - "from botorch.models import SingleTaskGP\n", - "from torch import nn\n", - "from botorch.optim import optimize_acqf\n", - "from botorch.acquisition import AcquisitionFunction\n", - "from gpytorch.mlls import ExactMarginalLogLikelihood\n", "import random\n", - "import numpy as np\n", "import torch\n", "\n", "def set_all_seeds(seed: int = 42):\n", @@ -39,13 +25,12 @@ " torch.backends.cudnn.benchmark = False\n", "\n", "seed = 42\n", - "set_all_seeds(seed)\n", - "\n" + "set_all_seeds(seed)" ] }, { "cell_type": "markdown", - "id": "cca0acae", + "id": "1", "metadata": {}, "source": [ "# Load the dataset" @@ -53,23 +38,13 @@ }, { "cell_type": "code", - "execution_count": 2, - "id": "c513a24b", + "execution_count": null, + "id": "2", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxYAAAHqCAYAAACZcdjsAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAABFbklEQVR4nO3deXgUZb728bs6JOkEEkL2RMIiuyAuERFZBgTZlAFh3JFFRp0ZQCVydJiXOYgbKoPigqhnFNQBdXREHR1BYWRRQSWCDBqibIaddBCykA4xXe8fHPoQEiDdlU51J9/PdeW66KfrqfpVdfVyU8tjmKZpCgAAAAAscNhdAAAAAIDQR7AAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawABB07r//fhmGUamtVatWGjduXMCXvXPnThmGoYULF3rbxo0bpyZNmgR82ScYhqH777+/zpbnj6+//lqXX365GjduLMMwtHHjRrtL8qpu/znbtC6XK8BVVVbT1zgU1gUATiBYAKi3/vWvfwXtD/Rgru1sysvLde211+rQoUN68skn9dprr6lly5Z2l3VGjzzyiN599127y6gVdq/L+++/r4svvlhOp1MtWrTQjBkz9Msvv9So78MPP6xf//rXSklJCYkADcA3BAsAISE3N1f/8z//41Off/3rX5o5c6ZPfVq2bKnS0lLdcsstPvXz1ZlqKy0t1fTp0wO6fCu2bdumn376SVOnTtXtt9+u0aNHq1mzZnaX5TV9+nSVlpZWarP7x/iprLzGdq7LRx99pBEjRiguLk7PPPOMRowYoYceekiTJ0+uUf/p06fr66+/1kUXXRTgSgHYoZHdBQBATURGRgZ0/r/88os8Ho8iIiLkdDoDuqyzsXv5Z3Pw4EFJUlxcnL2FnEajRo3UqFFwf70F+2t8OlOnTlXXrl318ccfe7dxbGysHnnkEd11113q2LHjGfvv2LFDrVq1ksvlUlJSUl2UDKAOccQCgK0+++wzdevWTU6nU23atNELL7xQ7XSnXmNRXl6umTNnql27dnI6nUpISFCvXr30ySefSDp+XcS8efMkHT+f/cSf9H/XUfzlL3/R3Llz1aZNG0VGRur777+v9hqLE7Zv365BgwapcePGSk9P1wMPPCDTNL3Pr1y5UoZhaOXKlZX6nTrPM9V2ou3UU0Q2bNigIUOGKDY2Vk2aNFH//v21bt26StMsXLhQhmHo888/V1ZWlpKSktS4cWNdc801ys/Pr/4FOMW///1v9e7dW40bN1ZcXJyGDx+unJwc7/Pjxo3Tr371K0nStddeK8Mw1Ldv32rntX37dhmGoSeffLLKc1988YUMw9Drr79ebV/TNJWYmKisrCxvm8fjUVxcnMLCwnT48GFv+2OPPaZGjRqpuLhYUtXrEgzDUElJiV555RXvtj71ep3Dhw9r3LhxiouLU9OmTTV+/HgdPXr0jNvq6aefrlLLnDlzZBhGpborKioUExOj++67r1JNp77GNXkvBGpdauL777/X999/r9tvv71ScPvDH/4g0zT19ttvn3UerVq1slwHgOAV3P+lA6Be+89//qOBAwcqKSlJ999/v3755RfNmDFDKSkpZ+17//33a9asWfrtb3+rSy+9VIWFhVq/fr2++eYbXXnllbrjjju0d+9effLJJ3rttdeqnceCBQvkdrt1++23KzIyUvHx8fJ4PNVOW1FRocGDB+uyyy7T448/rqVLl3rPLX/ggQd8Wu+a1Hay7777Tr1791ZsbKzuvfdehYeH64UXXlDfvn21atUqde/evdL0kydPVrNmzTRjxgzt3LlTc+fO1aRJk/Tmm2+ecTnLly/XkCFDdO655+r+++9XaWmpnnnmGfXs2VPffPONWrVqpTvuuEPnnHOOHnnkEd15553q1q3baV+vc889Vz179tSiRYs0ZcqUSs8tWrRIMTExGj58eLV9DcNQz549tXr1am/bpk2bdOTIETkcDn3++ee66qqrJElr1qzRRRdddNoL7F977TXvfnL77bdLktq0aVNpmuuuu06tW7fWrFmz9M033+ivf/2rkpOT9dhjj512e/Xu3Vsej0efffaZrr76am8tDodDa9as8U63YcMGFRcXq0+fPqedV03fC7W1LkeOHFF5eflp6znB6XR6t+uGDRskSZdcckmladLT09W8eXPv8wAaMBMAbDJixAjT6XSaP/30k7ft+++/N8PCwsxTP55atmxpjh071vv4ggsuMK+66qozzn/ixIlV5mOaprljxw5TkhkbG2sePHiw2ucWLFjgbRs7dqwpyZw8ebK3zePxmFdddZUZERFh5ufnm6Zpmp9++qkpyfz000/POs/T1WaapinJnDFjhvfxiBEjzIiICHPbtm3etr1795oxMTFmnz59vG0LFiwwJZkDBgwwPR6Pt33KlClmWFiYefjw4WqXd8KFF15oJicnmwUFBd62b7/91nQ4HOaYMWO8bSfW86233jrj/EzTNF944QVTkpmTk+NtO3bsmJmYmFjp9azO7NmzzbCwMLOwsNA0TdN8+umnzZYtW5qXXnqped9995mmaZoVFRVmXFycOWXKFG+/GTNmVNm2jRs3rnZ5J6a99dZbK7Vfc801ZkJCwhnrq6ioMGNjY817773XNM3j+0RCQoJ57bXXmmFhYWZRUZFpmqb5xBNPmA6Hw/z555+9fat7jWv6XqiNdfnVr35lSjrr38nLmT17tinJzMvLq7Lsbt26mZdddtkZt9fJ8vPzq2wDAKGPU6EA2KKiokLLli3TiBEj1KJFC297p06dNGjQoLP2j4uL03fffacff/zR7xpGjRrl03nekyZN8v7bMAxNmjRJx44d0/Lly/2u4WwqKir08ccfa8SIETr33HO97Wlpabrpppv02WefqbCwsFKf22+/vdKpQL1791ZFRYV++umn0y5n37592rhxo8aNG6f4+Hhve9euXXXllVfqX//6l1/1X3fddXI6nVq0aJG3bdmyZXK5XBo9evQZ+56o+4svvpB0/GhA79691bt3b+8Rgc2bN+vw4cPq3bu3X/Wd8Lvf/a7KsgsKCqps25M5HA5dfvnl3qMqOTk5Kigo0B//+EeZpqm1a9d66+7Spctpr0mx+l7wZ13mzJmjTz755Kx/9957r7fPiQviq7veyel0VrlgHkDDQ7AAYIv8/HyVlpaqXbt2VZ7r0KHDWfs/8MADOnz4sNq3b6/zzz9f//Vf/6VNmzb5VEPr1q1rPK3D4aj0w16S2rdvL+n4NRSBkp+fr6NHj1a7TTp16iSPx6Ndu3ZVaj/5x6kk7x2bfv7559Mu50ToON1yXC6XSkpKfK4/Li5Ow4YN0+LFi71tixYt0jnnnKMrrrjijH0vvvhiRUdHe0PEiWDRp08frV+/Xm632/tcr169fK7tZP5sM+n4j/bs7GyVlpZqzZo1SktL08UXX6wLLrjAW9tnn312xuBj9b1wqpqsS2ZmpgYMGHDWv/POO8/bJyoqSpJUVlZWZZlut9v7PICGi2ABICT16dNH27Zt08svv6wuXbror3/9qy6++GL99a9/rfE8avuH0OkGMquoqKjV5ZxNWFhYte3mSRea16UxY8Zo+/bt+uKLL1RUVKT3339fN954oxyOM38FhYeHq3v37lq9erW2bt2q/fv3q3fv3urVq5fKy8v15Zdfas2aNerYsaPlOwz5u81O1LJ27Vpv8JHkPaqyZcsW5efnWz6i4ouarMuhQ4e0f//+s/4dOXLE2yctLU3S8aNbp9q3b5/S09NreU0AhBqCBQBbJCUlKSoqqtpTmXJzc2s0j/j4eI0fP16vv/66du3apa5du1a6005NRyyuCY/Ho+3bt1dq++GHHyT9351uTvzP8Ml3CZJU7SlINa0tKSlJ0dHR1W6TLVu2yOFwKCMjo0bzOpMTA9ydbjmJiYlq3LixX/MePHiwkpKStGjRIi1ZskRHjx6t8TghvXv31ldffaXly5crMTFRHTt2VHx8vDp37qw1a9ZozZo1Z7wo+oTa3BdOdumllyoiIsJby4kA0adPH3355ZdasWKF9/Hp+PpeqI11GTlypNLS0s76d9ddd3n7XHjhhZKk9evXV5rX3r17tXv3bu/zABou7goFwBZhYWEaNGiQ3n33XeXl5XlP38jJydGyZcvO2r+goEAJCQnex02aNFHbtm0rnRZ04ofw4cOHa2XMhWeffVZPP/20pOP/+/vss88qPDxc/fv3l3T8x3lYWJhWr16tESNGePs999xzVeZV09rCwsI0cOBAvffee9q5c6c3xBw4cECLFy9Wr169FBsba3nd0tLSdOGFF+qVV17RtGnTvDVt3rxZH3/88VmvhziTRo0a6cYbb9TixYuVk5Oj888/X127dq1R3969e+uBBx7Q3Llz1atXL++P6t69e+u1117T3r17a3Q0oHHjxlUCX21wOp3q1q2bXn/9deXl5VU6YlFaWqqnn35abdq08f5vf3V8fS/UxrrMmTPnrKd5Sap0FKJz587q2LGjXnzxRd1xxx3eIyPz58+XYRj6zW9+4532yJEj2rdvn9LS0tS0aVNLtQIIHQQLALaZOXOmli5dqt69e+sPf/iDfvnlFz3zzDPq3LnzWa+XOO+889S3b19lZmYqPj5e69ev19tvv13pAuvMzExJ0p133qlBgwYpLCxMN9xwg1+1Op1OLV26VGPHjlX37t310Ucf6cMPP9Sf/vQn72k4TZs21bXXXqtnnnlGhmGoTZs2+uCDD7wDyp3Ml9oeeughffLJJ+rVq5f+8Ic/qFGjRnrhhRdUVlamxx9/3K/1qc7s2bM1ZMgQ9ejRQxMmTPDebrZp06ZVxlzw1ZgxY/T000/r008/PeMtXE/Vo0cPNWrUSLm5ud7bq0rHjwDMnz9fkmoULDIzM7V8+XI98cQTSk9PV+vWravcptdfvXv31qOPPqqmTZvq/PPPlyQlJyerQ4cOys3NrTLORHV8eS/Uxrqc2P98NXv2bP3617/WwIEDdcMNN2jz5s169tln9dvf/ladOnXyTrdkyRKNHz9eCxYsqLT+r732mn766SfvuBqrV6/WQw89JEm65ZZbvEfOAIQoW+9JBaDBW7VqlZmZmWlGRESY5557rvn8889Xe7vQU283+9BDD5mXXnqpGRcXZ0ZFRZkdO3Y0H374YfPYsWPeaX755Rdz8uTJZlJSkmkYhneeJ27/Onv27Cr1nO52s40bNza3bdtmDhw40IyOjjZTUlLMGTNmmBUVFZX65+fnm6NGjTKjo6PNZs2amXfccYe5efPmKvM8XW2mWfVWpKZpmt988405aNAgs0mTJmZ0dLTZr18/84svvqg0zYnbzX799deV2k93G9zqLF++3OzZs6cZFRVlxsbGmsOGDTO///77audXk9vNnqxz586mw+Ewd+/e7VO/bt26mZLML7/80tu2e/duU5KZkZFRZfrq9p8tW7aYffr0MaOioirdRvXEtCduGXzCiW25Y8eOs9b34YcfmpLMIUOGVGr/7W9/a0oyX3rppSp9qnuNa/peCOS61MSSJUvMCy+80IyMjDSbN29uTp8+vdL77uRlnrzPm+aZb3Nbk/0TQHAzTNOmq/kAAA3KRRddpPj4eO91BwCA+oWLtwEAAbd+/Xpt3LhRY8aMsbsUAECAcMQCABAwmzdvVnZ2tubMmSOXy6Xt27fL6XTaXRYAIAA4YgEACJi3335b48ePV3l5uV5//XVCBQDUYxyxAAAAAGAZRywAAAAAWEawAAAAAGBZvR8gz+PxaO/evYqJifGO2AoAAADg7EzTVFFRkdLT0+VwnPmYRL0PFnv37lVGRobdZQAAAAAha9euXWrevPkZp6n3wSImJkbS8Y0RGxtrczUAAABA6CgsLFRGRob3N/WZ1PtgceL0p9jYWIIFAAAA4IeaXFLAxdsAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsa2V0AAARaXl6eXC6XX30TExPVokWLWq4IAID6h2ABoF7Ly8tTh46d5C496ld/Z1S0crfkEC4AADgLggWAes3lcsldelQJV9+j8IQMn/qWF+xSwQdz5HK5CBYAAJwFwQJAgxCekKHI1LZ2lwEAQL3FxdsAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwzNZgMX/+fHXt2lWxsbGKjY1Vjx499NFHH3mf79u3rwzDqPT3u9/9zsaKAQAAAFSnkZ0Lb968uR599FG1a9dOpmnqlVde0fDhw7VhwwZ17txZknTbbbfpgQce8PaJjo62q1wAAAAAp2FrsBg2bFilxw8//LDmz5+vdevWeYNFdHS0UlNT7SgPAAAAQA0FzTUWFRUVeuONN1RSUqIePXp42xctWqTExER16dJF06ZN09GjR22sEgAAAEB1bD1iIUn/+c9/1KNHD7ndbjVp0kRLlizReeedJ0m66aab1LJlS6Wnp2vTpk267777lJubq3feeee08ysrK1NZWZn3cWFhoSTJ4/HI4/EEdmUABB3TNOVwOOQwJIdMn/o6DMnhcMg0TT4/AAANki/ff7YHiw4dOmjjxo06cuSI3n77bY0dO1arVq3Seeedp9tvv9073fnnn6+0tDT1799f27ZtU5s2baqd36xZszRz5swq7fn5+XK73QFbDwDBye12KzMzU3EpUQpv5luwKFeUzsnMlNvt1sGDBwNUIQAAwauoqKjG0xqmafr2TRtgAwYMUJs2bfTCCy9Uea6kpERNmjTR0qVLNWjQoGr7V3fEIiMjQz///LNiY2MDVjeA4LRhwwZdeumlSh3zhCJTqv8PidMpO7BN+1/N0ldffaWLLrooQBUCABC8CgsL1axZMx05cuSsv6VtP2JxKo/HUykYnGzjxo2SpLS0tNP2j4yMVGRkZJV2h8MhhyNoLikBUEcMwzh+KqQpeWT41NdjHv9MMgyDzw8AQIPky/efrcFi2rRpGjJkiFq0aKGioiItXrxYK1eu1LJly7Rt2zYtXrxYQ4cOVUJCgjZt2qQpU6aoT58+6tq1q51lAwAAADiFrcHi4MGDGjNmjPbt26emTZuqa9euWrZsma688krt2rVLy5cv19y5c1VSUqKMjAyNGjVK06dPt7NkAAAAANWwNVi89NJLp30uIyNDq1atqsNqAAAAAPiLk4YBAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJbZGizmz5+vrl27KjY2VrGxserRo4c++ugj7/Nut1sTJ05UQkKCmjRpolGjRunAgQM2VgwAAACgOrYGi+bNm+vRRx9Vdna21q9fryuuuELDhw/Xd999J0maMmWK/vnPf+qtt97SqlWrtHfvXo0cOdLOkgEAAABUo5GdCx82bFilxw8//LDmz5+vdevWqXnz5nrppZe0ePFiXXHFFZKkBQsWqFOnTlq3bp0uu+wyO0oGAAAAUI2gucaioqJCb7zxhkpKStSjRw9lZ2ervLxcAwYM8E7TsWNHtWjRQmvXrrWxUgAAAACnsvWIhST95z//UY8ePeR2u9WkSRMtWbJE5513njZu3KiIiAjFxcVVmj4lJUX79+8/7fzKyspUVlbmfVxYWChJ8ng88ng8AVkHAMHLNE05HA45DMkh06e+DkNyOBwyTZPPDwBAg+TL95/twaJDhw7auHGjjhw5orfffltjx47VqlWr/J7frFmzNHPmzCrt+fn5crvdVkoFEILcbrcyMzMVlxKl8Ga+BYtyRemczEy53W4dPHgwQBUCABC8ioqKajyt7cEiIiJCbdu2lSRlZmbq66+/1lNPPaXrr79ex44d0+HDhysdtThw4IBSU1NPO79p06YpKyvL+7iwsFAZGRlKSkpSbGxswNYDQHDas2ePsrOzldr5ZkXK8Klv2YFS7c/OltPpVHJycoAqBAAgeDmdzhpPa3uwOJXH41FZWZkyMzMVHh6uFStWaNSoUZKk3Nxc5eXlqUePHqftHxkZqcjIyCrtDodDDkfQXFICoI4YhnH8VEhT8vgYLDzm8c8kwzD4/AAANEi+fP/ZGiymTZumIUOGqEWLFioqKtLixYu1cuVKLVu2TE2bNtWECROUlZWl+Ph4xcbGavLkyerRowd3hAIAAACCjK3B4uDBgxozZoz27dunpk2bqmvXrlq2bJmuvPJKSdKTTz4ph8OhUaNGqaysTIMGDdJzzz1nZ8kAAAAAqmFrsHjppZfO+LzT6dS8efM0b968OqoIAAAAgD84aRgAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZY3sLgAAUD/k5eXJ5XL53C8xMVEtWrQIQEUAgLpEsAAAWJaXl6cOHTvJXXrU577OqGjlbskhXABAiCNYAAAsc7lccpceVcLV9yg8IaPG/coLdqnggzlyuVwECwAIcQQLAECtCU/IUGRqW7vLAADYgIu3AQAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBltgaLWbNmqVu3boqJiVFycrJGjBih3NzcStP07dtXhmFU+vvd735nU8UAAAAAqmNrsFi1apUmTpyodevW6ZNPPlF5ebkGDhyokpKSStPddttt2rdvn/fv8ccft6liAAAAANVpZOfCly5dWunxwoULlZycrOzsbPXp08fbHh0drdTU1LouDwAAAEAN2RosTnXkyBFJUnx8fKX2RYsW6W9/+5tSU1M1bNgw/fnPf1Z0dHS18ygrK1NZWZn3cWFhoSTJ4/HI4/EEqHIAwco0TTkcDjkMySHTp74OQ3I4HDJNk8+Ps/B3O7ONASC4+fLZHDTBwuPx6O6771bPnj3VpUsXb/tNN92kli1bKj09XZs2bdJ9992n3NxcvfPOO9XOZ9asWZo5c2aV9vz8fLnd7oDVDyA4ud1uZWZmKi4lSuHNfAsW5YrSOZmZcrvdOnjwYIAqrB/83c5sYwAIbkVFRTWeNmiCxcSJE7V582Z99tlnldpvv/1277/PP/98paWlqX///tq2bZvatGlTZT7Tpk1TVlaW93FhYaEyMjKUlJSk2NjYwK0AgKC0Z88eZWdnK7XzzYqU4VPfsgOl2p+dLafTqeTk5ABVWD/4u53ZxgAQ3JxOZ42nDYpgMWnSJH3wwQdavXq1mjdvfsZpu3fvLknaunVrtcEiMjJSkZGRVdodDoccDu6uCzQ0hmEcPxXSlDw+BguPefxoqmEYfH6chb/bmW0MAMHNl89mW4OFaZqaPHmylixZopUrV6p169Zn7bNx40ZJUlpaWoCrAwAAAFBTtgaLiRMnavHixXrvvfcUExOj/fv3S5KaNm2qqKgobdu2TYsXL9bQoUOVkJCgTZs2acqUKerTp4+6du1qZ+kAAAAATmJrsJg/f76k44PgnWzBggUaN26cIiIitHz5cs2dO1clJSXKyMjQqFGjNH36dBuqBQAAAHA6tp8KdSYZGRlatWpVHVUDAAAAwF9cKQcAAADAMoIFAAAAAMuC4nazAADUtby8PLlcLr/6JiYmqkWLFrVcEQCENoIFAKDBycvLU4eOneQuPepXf2dUtHK35BAuAOAkBAsAQIPjcrnkLj2qhKvvUXhChk99ywt2qeCDOXK5XAQLADgJwQIA0GCFJ2QoMrWt3WUAQL3AxdsAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAs8zlYbN++PRB1AAAAAAhhPgeLtm3bql+/fvrb3/4mt9sdiJoAAAAAhBifx7H45ptvtGDBAmVlZWnSpEm6/vrrNWHCBF166aWBqA8AAKBO5eXlyeVy+dU3MTGRgRPRYPkcLC688EI99dRTmjNnjt5//30tXLhQvXr1Uvv27XXrrbfqlltuUVJSUiBqBQAACKi8vDx16NhJ7tKjfvV3RkUrd0sO4QINkt8jbzdq1EgjR47UVVddpeeee07Tpk3T1KlT9ac//UnXXXedHnvsMaWlpdVmrQAAAAHlcrnkLj2qhKvvUXhChk99ywt2qeCDOXK5XAQLNEh+B4v169fr5Zdf1htvvKHGjRtr6tSpmjBhgnbv3q2ZM2dq+PDh+uqrr2qzVgAAgDoRnpChyNS2dpcBhBSfg8UTTzyhBQsWKDc3V0OHDtWrr76qoUOHyuE4fh1469attXDhQrVq1aq2awUAAAAQpHwOFvPnz9ett96qcePGnfZUp+TkZL300kuWiwMAAAAQGnwOFj/++ONZp4mIiNDYsWP9KggAAABA6PF5HIsFCxborbfeqtL+1ltv6ZVXXqmVogAAAACEFp+DxaxZs5SYmFilPTk5WY888kitFAUAAAAgtPgcLPLy8tS6desq7S1btlReXl6tFAUAAAAgtPh8jUVycrI2bdpU5a5P3377rRISEmqrLgAIGjk5OX71YwReAEBD4nOwuPHGG3XnnXcqJiZGffr0kSStWrVKd911l2644YZaLxAA7FJR/LNkGBo9erRf/RmBFwDQkPgcLB588EHt3LlT/fv3V6NGx7t7PB6NGTOGaywA1CuesmLJNBmBFwCAGvA5WEREROjNN9/Ugw8+qG+//VZRUVE6//zz1bJly0DUBwC2YwReAADOzudgcUL79u3Vvn372qwFAAAAQIjyOVhUVFRo4cKFWrFihQ4ePCiPx1Pp+X//+9+1VhwAAACA0OBzsLjrrru0cOFCXXXVVerSpYsMwwhEXQAAAABCiM/B4o033tDf//53DR06NBD1AAAAAAhBPg+QFxERobZtuYgRAAAAwP/xOVjcc889euqpp2SaZiDqAQAAABCCfD4V6rPPPtOnn36qjz76SJ07d1Z4eHil5995551aKw4AAABAaPA5WMTFxemaa64JRC0AAAAAQpTPwWLBggWBqAMAAABACPP5GgtJ+uWXX7R8+XK98MILKioqkiTt3btXxcXFtVocAAAAgNDg8xGLn376SYMHD1ZeXp7Kysp05ZVXKiYmRo899pjKysr0/PPPB6JOAAAAAEHM5yMWd911ly655BL9/PPPioqK8rZfc801WrFiRa0WBwAAACA0+HzEYs2aNfriiy8UERFRqb1Vq1bas2dPrRUGAAAAIHT4fMTC4/GooqKiSvvu3bsVExPj07xmzZqlbt26KSYmRsnJyRoxYoRyc3MrTeN2uzVx4kQlJCSoSZMmGjVqlA4cOOBr2QAAAAACyOdgMXDgQM2dO9f72DAMFRcXa8aMGRo6dKhP81q1apUmTpyodevW6ZNPPlF5ebkGDhyokpIS7zRTpkzRP//5T7311ltatWqV9u7dq5EjR/paNgAAAIAA8vlUqDlz5mjQoEE677zz5Ha7ddNNN+nHH39UYmKiXn/9dZ/mtXTp0kqPFy5cqOTkZGVnZ6tPnz46cuSIXnrpJS1evFhXXHGFpOO3u+3UqZPWrVunyy67zNfyAQAAAASAz8GiefPm+vbbb/XGG29o06ZNKi4u1oQJE3TzzTdXupjbH0eOHJEkxcfHS5Kys7NVXl6uAQMGeKfp2LGjWrRoobVr11YbLMrKylRWVuZ9XFhYKOn4KVwej8dSfQBCj2macjgcchiSQ6ZPfR2GLPc1TbNBfPb4u53t2k61sV80lNe2oWHfACrzZV/2OVhIUqNGjTR69Gh/up6Wx+PR3XffrZ49e6pLly6SpP379ysiIkJxcXGVpk1JSdH+/furnc+sWbM0c+bMKu35+flyu921WjOA4Od2u5WZmam4lCiFN/PtR4I7I15FfvYtV5TOycyU2+3WwYMHfeobivzdznZtJyv7RUN7bRsa9g2gshNj1tWEz8Hi1VdfPePzY8aM8XWWkqSJEydq8+bN+uyzz/zqf8K0adOUlZXlfVxYWKiMjAwlJSUpNjbW0rwBhJ49e/YoOztbqZ1vVqQMn/oW7zqkAj/7lh0o1f7sbDmdTiUnJ/vUNxT5u53t2k5W9ouG9to2NOwbQGVOp7PG0/ocLO66665Kj8vLy3X06FFFREQoOjrar2AxadIkffDBB1q9erWaN2/ubU9NTdWxY8d0+PDhSkctDhw4oNTU1GrnFRkZqcjIyCrtDodDDodfA40DCGGGYRw/FdKUPD7+SPCYstzXMIwG8dnj73a2azvVxn7RUF7bhoZ9A6jMl33Z573+559/rvRXXFys3Nxc9erVy+eLt03T1KRJk7RkyRL9+9//VuvWrSs9n5mZqfDw8EoD7+Xm5iovL089evTwtXQAAAAAAeLXNRanateunR599FGNHj1aW7ZsqXG/iRMnavHixXrvvfcUExPjvW6iadOmioqKUtOmTTVhwgRlZWUpPj5esbGxmjx5snr06MEdoQAAAIAgUivBQjp+QffevXt96jN//nxJUt++fSu1L1iwQOPGjZMkPfnkk3I4HBo1apTKyso0aNAgPffcc7VRMgAAAIBa4nOweP/99ys9Nk1T+/bt07PPPquePXv6NC/TPPvdFpxOp+bNm6d58+b5NG8AAAAAdcfnYDFixIhKjw3DUFJSkq644grNmTOntuoCAAAAEEJ8DhYM+AIAAADgVNwLDQAAAIBlPh+xOHnwubN54oknfJ09AAAAgBDkc7DYsGGDNmzYoPLycnXo0EGS9MMPPygsLEwXX3yxdzrD8G1QGQAAAAChy+dgMWzYMMXExOiVV15Rs2bNJB0fNG/8+PHq3bu37rnnnlovEgAAAEBw8/kaizlz5mjWrFneUCFJzZo100MPPcRdoQAAAIAGyudgUVhYqPz8/Crt+fn5KioqqpWiAAAAAIQWn4PFNddco/Hjx+udd97R7t27tXv3bv3jH//QhAkTNHLkyEDUCAAAACDI+XyNxfPPP6+pU6fqpptuUnl5+fGZNGqkCRMmaPbs2bVeIAAAAIDg53OwiI6O1nPPPafZs2dr27ZtkqQ2bdqocePGtV4cAAAAgNDg9wB5+/bt0759+9SuXTs1btxYpmnWZl0AAAAAQojPwaKgoED9+/dX+/btNXToUO3bt0+SNGHCBG41CwAAADRQPgeLKVOmKDw8XHl5eYqOjva2X3/99Vq6dGmtFgcAAAAgNPh8jcXHH3+sZcuWqXnz5pXa27Vrp59++qnWCgMAAAAQOnw+YlFSUlLpSMUJhw4dUmRkZK0UBQAAACC0+BwsevfurVdffdX72DAMeTwePf744+rXr1+tFgcAAAAgNPh8KtTjjz+u/v37a/369Tp27Jjuvfdefffddzp06JA+//zzQNQIAAAAIMj5fMSiS5cu+uGHH9SrVy8NHz5cJSUlGjlypDZs2KA2bdoEokYAAAAAQc6nIxbl5eUaPHiwnn/+ef2///f/AlUTAAAAgBDj0xGL8PBwbdq0KVC1AAAAAAhRPp8KNXr0aL300kuBqAUAAABAiPL54u1ffvlFL7/8spYvX67MzEw1bty40vNPPPFErRUHAGgYcnJy/OqXmJioFi1a1HI1AAB/1ChYbNq0SV26dJHD4dDmzZt18cUXS5J++OGHStMZhlH7FQIA6q2K4p8lw9Do0aP96u+MilbulhzCBQAEgRoFi4suukj79u1TcnKyfvrpJ3399ddKSEgIdG0AgHrOU1YsmaYSrr5H4QkZPvUtL9ilgg/myOVyESwAIAjUKFjExcVpx44dSk5O1s6dO+XxeAJdFwCgAQlPyFBkalu7ywAAWFCjYDFq1Cj96le/UlpamgzD0CWXXKKwsLBqp92+fXutFggAAAAg+NUoWLz44osaOXKktm7dqjvvvFO33XabYmJiAl0bAAAAgBBR47tCDR48WJKUnZ2tu+66i2ABAAAAwMvn280uWLAgEHUAAAAACGE+D5AHAAAAAKciWAAAAACwjGABAAAAwDKfr7EAAAReXl6eXC6XX30TExMZMA4AUOcIFgAQZPLy8tShYye5S4/61d8ZFa3cLTmECwBAnSJYAECQcblccpceVcLV9yg8IcOnvuUFu1TwwRy5XC6CBQCgThEsACBIhSdkKDK1rd1lAABQI1y8DQAAAMAyggUAAAAAy2wNFqtXr9awYcOUnp4uwzD07rvvVnp+3LhxMgyj0t/gwYPtKRYAAADAadkaLEpKSnTBBRdo3rx5p51m8ODB2rdvn/fv9ddfr8MKAQAAANSErRdvDxkyREOGDDnjNJGRkUpNTa2jigAAAAD4I+ivsVi5cqWSk5PVoUMH/f73v1dBQYHdJQEAAAA4RVDfbnbw4MEaOXKkWrdurW3btulPf/qThgwZorVr1yosLKzaPmVlZSorK/M+LiwslCR5PB55PJ46qRtA8DBNUw6HQw5Dcsj0qa/DkOW+pmn6/NlTGzX7s1wr/K2ZbYxg0xD3jV27dsnlcvncLzExURkZvo21g9Djy74c1MHihhtu8P77/PPPV9euXdWmTRutXLlS/fv3r7bPrFmzNHPmzCrt+fn5crvdAasVQHByu93KzMxUXEqUwpv59iPBnRGvIj/7litK52Rmyu126+DBg74t10LNVpZrhb81s40RbBravpGfn6/f/f4PKj9WdvaJTxEeEann5z+npKSkAFSGYFFUVFTjaYM6WJzq3HPPVWJiorZu3XraYDFt2jRlZWV5HxcWFiojI0NJSUmKjY2tq1IBBIk9e/YoOztbqZ1vVqQMn/oW7zqkAj/7lh0o1f7sbDmdTiUnJ/vU10rNVpZrhb81s40RbBravrFnzx6tW/uFEq6aovCEmh99KC/YpYIPn9SxY8dCZl3hH6fTWeNpQypY7N69WwUFBUpLSzvtNJGRkYqMjKzS7nA45HAE/SUlAGqZYRjHT4U0JY+PPxI8piz3NQzD58+e2qjZn+Va4W/NbGMEm4a2b5xY37D4DIWntK1xv1BcV/jHl9fX1mBRXFysrVu3eh/v2LFDGzduVHx8vOLj4zVz5kyNGjVKqamp2rZtm+699161bdtWgwYNsrFqAAAAAKeyNVisX79e/fr18z4+cQrT2LFjNX/+fG3atEmvvPKKDh8+rPT0dA0cOFAPPvhgtUckAAAAANjH1mDRt29fmebpL4xatmxZHVYDAAAAwF+cFAcAAADAMoIFAAAAAMsIFgAAAAAsC6nbzQIAAKCqvLw8v0bPzsnJCUA1aKgIFgAAACEsLy9PHTp2krv0qN2loIEjWAAAAIQwl8sld+lRJVx9j0+jZ0tS6fb1OrLmbwGqDA0NwQIAAKAeCE/IUGRqzUfPlqTygl0BqgYNERdvAwAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAybjcLAPWQv6PpJiYmqkWLFrVcDQCgISBYAEA9UlH8s2QYGj16tF/9nVHRyt2SQ7gAAPiMYAEA9YinrFgyTb9G4C0v2KWCD+bI5XIRLAAAPiNYAEA95M8IvAAAWMHF2wAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjNvNAg1QXl6eXC6XX30ZmRmwhvcfgPqKYAE0MHl5eerQsZPcpUf96s/IzID/eP8BqM8IFkAD43K55C49ysjMgA14/wGozwgWQAPFyMyAfXj/AaiPuHgbAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJZxu1kAAAD4JScnx69+jCJfPxEsAAAA4JOK4p8lw9Do0aP96s8o8vUTwQIAAAA+8ZQVS6bJKPKohGABAAAAvzCKPE7GxdsAAAAALCNYAAAAALCMYAEAAADAMluDxerVqzVs2DClp6fLMAy9++67lZ43TVP//d//rbS0NEVFRWnAgAH68ccf7SkWAAAAwGnZGixKSkp0wQUXaN68edU+//jjj+vpp5/W888/ry+//FKNGzfWoEGD5Ha767hSAAAAAGdi612hhgwZoiFDhlT7nGmamjt3rqZPn67hw4dLkl599VWlpKTo3Xff1Q033FCXpQIAAAA4g6C93eyOHTu0f/9+DRgwwNvWtGlTde/eXWvXrj1tsCgrK1NZWZn3cWFhoSTJ4/HI4/EEtmigDu3atUsul8vnflu2bJHD4ZDDkBwyferrMCSHwyHTNOv8/WTn+tb1tjJNs8HUHGr1hupyUXOh+BrZ8Zlh13sXdc+X1yhog8X+/fslSSkpKZXaU1JSvM9VZ9asWZo5c2aV9vz8fE6hQr2Rn5+v3/3+Dyo/Vnb2iauRmZmpuJQohTfz7cugXFE6JzNTbrdbBw8e9GvZ/rBrfd0Z8SqyYVu53e4GU3Oo1Ruqy0XNheJrZMdnhl3vXdS9oqKiGk8btMHCX9OmTVNWVpb3cWFhoTIyMpSUlKTY2FgbKwNqz549e7Ru7RdKuGqKzyOelm7P1pHPFim1882KlOFT37IDpdqfnS2n06nk5GSf+lph1/oW7zqkguzsOt9We/bsUbafyw21mkOt3lBdLmouFF8jOz4z7Hrvou45nc4aTxu0wSI1NVWSdODAAaWlpXnbDxw4oAsvvPC0/SIjIxUZGVml3eFwyOHg7rqoHwzDkMfjUVh8hsJTfBvxtMy16/ipgabk8fHLwGMePyRqGEadvp/sXt+63lYn1rch1Bxq9YbqclFzofga2fGZYdd7F3XPl9coaF/N1q1bKzU1VStWrPC2FRYW6ssvv1SPHj1srAwAAADAqWw9YlFcXKytW7d6H+/YsUMbN25UfHy8WrRoobvvvlsPPfSQ2rVrp9atW+vPf/6z0tPTNWLECPuKBgAAAFCFrcFi/fr16tevn/fxiWsjxo4dq4ULF+ree+9VSUmJbr/9dh0+fFi9evXS0qVLfTrXCwAAAEDg2Ros+vbtK9M8/Z0EDMPQAw88oAceeKAOqwIAAADgq6C9xgIAAABA6CBYAAAAALCMYAEAAADAsqAdxwIA6oOcnJw66VObQq3mUKsXAOorggUABEBF8c+SYWj06NF2l1JjoVZzqNULAPUdwQIAAsBTViyZphKuvkfhCRk+9S3dvl5H1vwtQJWdXqjVHGr1AkB9R7AAgAAKT8hQZGpbn/qUF+wKUDU1E2o1h1q9AFBfcfE2AAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACzjdrP1XF5enlwul199ExMT1aJFi1quCABghb+jhvOZDiDQCBb1WF5enjp07CR36VG/+jujopW7JYcvIgAIAlZHGuczHUCgESzqMZfLJXfpUb9GpS0v2KWCD+bI5XLxJQQAQcDKSON8pgOoCwSLBsCfUWkBAMGJz3QAwYqLtwEAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGbebDQH+jp7t7+isocquUcYb4ujmjPwLhCbeuwACiWAR5KyOnt1Q2DXKeEMb3ZyRf4HQxHsXQF0gWAQ5K6Nnl25fryNr/hagyoKLXaOMN7TRzRn5FwhNvHcB1AWCRYjwZ6TV8oJdAaomeNk1Im1DGwm3oa0vUF/w3gUQSFy8DQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLuN0sgoqdo4z7M4+GNro5AIQKO79PgIaKYIGgYdco41ZHpAUABBe7vk+Aho5ggaBh1yjjVkakbUijmwNAqLDr+wRo6AgWCDp2jTLO6OYAUL/wuQ7ULS7eBgAAAGAZwQIAAACAZQQLAAAAAJYFdbC4//77ZRhGpb+OHTvaXRYAAACAUwT9xdudO3fW8uXLvY8bNQr6kgEAAIAGJ+h/pTdq1Eipqal2lwEAAADgDII+WPz4449KT0+X0+lUjx49NGvWLLVo0eK005eVlamsrMz7uLCwUJLk8Xjk8XgCXm9tM01TDodDDkNyyPSpr8OQ5b45OTkyTd/6SlJiYqIyMny7d7jd6xpK23jLli0hu76mafr8Xmyo+0ZD6Btq9Z7ct6Hty/6sr13s3s51va3sWN9Q3E7wjy+vUVAHi+7du2vhwoXq0KGD9u3bp5kzZ6p3797avHmzYmJiqu0za9YszZw5s0p7fn6+3G53oEuudW63W5mZmYpLiVJ4M9/euO6MeBX52bfMXaFzMjP11FNP+dTvhPCISD0//zklJSXVuI9d62pXX6vbONS2VbmidE5mptxutw4ePOjbchvYvtGQ+oZavVLD25etrK9d7NrOdm0rO9Y3FLcT/FNUVFTjaYM6WAwZMsT7765du6p79+5q2bKl/v73v2vChAnV9pk2bZqysrK8jwsLC5WRkaGkpCTFxsYGvObatmfPHmVnZyu1882KlOFT3+Jdh1Tgb9/te1WQna2Eq6b4PGppecEuFXz4pI4dO6bk5OQa97NtXe3qa2Ebl27P1pHPFoXU+pYdKNX+7Gw5nU6f9gupAe4bDahvqNUrNbx92cr62sWu7WzXtrJjfUNxO8E/TqezxtMGdbA4VVxcnNq3b6+tW7eedprIyEhFRkZWaXc4HHI4gvomWNUyDOP4aVym5PHxjesxZblvWHyGwlN8G7X0RF/DMHza5navayht4zLXrpBdX1/3C6nh7hsNoW+o1Xty34a2L/uzvnaxezvX9bayY31DcTvBP768RiH1ahYXF2vbtm1KS0uzuxQAAAAAJwnqYDF16lStWrVKO3fu1BdffKFrrrlGYWFhuvHGG+0uDQAAAMBJgvpUqN27d+vGG29UQUGBkpKS1KtXL61bt86nC4IBAAAABF5QB4s33njD7hIAAAAA1EBQnwoFAAAAIDQQLAAAAABYFtSnQgGof3JycuqkDwAAqFsECwB1oqL4Z8kwNHr0aLtLAQAAAUCwAFAnPGXFkmkq4ep7/BhpfL2OrPlbgCoDAAC1gWABoE6FJ2QoMtW3kcbLC3YFqBoAAFBbuHgbAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJZxu1kEjK+jJTO6MoBQ0tBGkfe39sTERLVo0aKWq0F9wD5V/xAsUOsYYRlAfdbQPuOsrq8zKlq5W3L4IQgv9qn6i2CBWufvCMuMrgwgFDS0UeStrG95wS4VfDBHLpeLH4HwYp+qvwgWCBhfR1hmdGUAoaShjSLvz/oCZ8I+Vf9w8TYAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALON2s3UkLy9PLpfL536hPEorAACS/99lZWVlioyMrLPl1RZGlK6f/P0tJzWc15ZgUQfy8vLUoWMnuUuP2l0KAAB1xvIo5YZDMj21W1QAMaJ0/WX1t1xDeW0JFnXA5XLJXXq0wYzSCgCAVDujlIfSdycjStdfVn7LNaTXlmBRhxraKK0AAEjWvv9C8buTEaXrL17bM+PibQAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnjWAAAAASJnJycOukT6vxd58TERNsGqQvFmn1FsAAAALBZRfHPkmFo9OjRdpcS1KxuJ2dUtHK35NTpD/VQrNlfBAsAAACbecqKJdNUwtX3KDwhw6e+pdvX68iavwWosuBiZTuVF+xSwQdz5HK56vRHeijW7C+CBQAAQJAIT8hQZGpbn/qUF+wKUDXBy5/tZLdQrNlXXLwNAAAAwDKCBQAAAADLQiJYzJs3T61atZLT6VT37t311Vdf2V0SAAAAgJMEfbB48803lZWVpRkzZuibb77RBRdcoEGDBungwYN2lwYAAADgfwV9sHjiiSd02223afz48TrvvPP0/PPPKzo6Wi+//LLdpQEAAAD4X0EdLI4dO6bs7GwNGDDA2+ZwODRgwACtXbvWxsoAAAAAnCyobzfrcrlUUVGhlJSUSu0pKSnasmVLtX3KyspUVlbmfXzkyBFJ0uHDh+XxeAJX7BkUFRXJMAyVH9gqlbt96ltxaHeD6Rtq9dKXvvS13jfU6qUvfekbmvVKUvn/9s3OzlZRUZFPfX/44Qdbay4qKtLhw4d96ltbCgsLJUmmaZ51WsOsyVQ22bt3r8455xx98cUX6tGjh7f93nvv1apVq/Tll19W6XP//fdr5syZdVkmAAAAUK/t2rVLzZs3P+M0QX3EIjExUWFhYTpw4ECl9gMHDig1NbXaPtOmTVNWVpb3scfj0aFDh5SQkCDDMAJab20pLCxURkaGdu3apdjYWLvLQT3APoVAYL9CbWOfQiCwX1ljmqaKioqUnp5+1mmDOlhEREQoMzNTK1as0IgRIyQdDworVqzQpEmTqu0TGRmpyMjISm1xcXEBrjQwYmNjeQOgVrFPIRDYr1Db2KcQCOxX/mvatGmNpgvqYCFJWVlZGjt2rC655BJdeumlmjt3rkpKSjR+/Hi7SwMAAADwv4I+WFx//fXKz8/Xf//3f2v//v268MILtXTp0ioXdAMAAACwT9AHC0maNGnSaU99qo8iIyM1Y8aMKqd0Af5in0IgsF+htrFPIRDYr+pOUN8VCgAAAEBoCOoB8gAAAACEBoIFAAAAAMsIFgAAAAAsI1gEsZ07d2rChAlq3bq1oqKi1KZNG82YMUPHjh2zuzSEuIcffliXX365oqOjQ3acF9hr3rx5atWqlZxOp7p3766vvvrK7pIQwlavXq1hw4YpPT1dhmHo3XfftbskhLhZs2apW7duiomJUXJyskaMGKHc3Fy7y6r3CBZBbMuWLfJ4PHrhhRf03Xff6cknn9Tzzz+vP/3pT3aXhhB37NgxXXvttfr9739vdykIQW+++aaysrI0Y8YMffPNN7rgggs0aNAgHTx40O7SEKJKSkp0wQUXaN68eXaXgnpi1apVmjhxotatW6dPPvlE5eXlGjhwoEpKSuwurV7jrlAhZvbs2Zo/f762b99udymoBxYuXKi7775bhw8ftrsUhJDu3burW7duevbZZyVJHo9HGRkZmjx5sv74xz/aXB1CnWEYWrJkiUaMGGF3KahH8vPzlZycrFWrVqlPnz52l1NvccQixBw5ckTx8fF2lwGggTp27Jiys7M1YMAAb5vD4dCAAQO0du1aGysDgNM7cuSIJPEbKsAIFiFk69ateuaZZ3THHXfYXQqABsrlcqmiokIpKSmV2lNSUrR//36bqgKA0/N4PLr77rvVs2dPdenSxe5y6jWChQ3++Mc/yjCMM/5t2bKlUp89e/Zo8ODBuvbaa3XbbbfZVDmCmT/7FQAA9d3EiRO1efNmvfHGG3aXUu81sruAhuiee+7RuHHjzjjNueee6/333r171a9fP11++eV68cUXA1wdQpWv+xXgj8TERIWFhenAgQOV2g8cOKDU1FSbqgKA6k2aNEkffPCBVq9erebNm9tdTr1HsLBBUlKSkpKSajTtnj171K9fP2VmZmrBggVyODjIhOr5sl8B/oqIiFBmZqZWrFjhvbjW4/FoxYoVmjRpkr3FAcD/Mk1TkydP1pIlS7Ry5Uq1bt3a7pIaBIJFENuzZ4/69u2rli1b6i9/+Yvy8/O9z/E/g7AiLy9Phw4dUl5enioqKrRx40ZJUtu2bdWkSRN7i0PQy8rK0tixY3XJJZfo0ksv1dy5c1VSUqLx48fbXRpCVHFxsbZu3ep9vGPHDm3cuFHx8fFq0aKFjZUhVE2cOFGLFy/We++9p5iYGO81YE2bNlVUVJTN1dVf3G42iC1cuPC0X9S8bLBi3LhxeuWVV6q0f/rpp+rbt2/dF4SQ8+yzz2r27Nnav3+/LrzwQj399NPq3r273WUhRK1cuVL9+vWr0j527FgtXLiw7gtCyDMMo9r2BQsWnPW0YfiPYAEAAADAMk7YBwAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAA2KZVq1aaO3eu3WUAAGoBwQIAAACAZQQLAAAAAJYRLAAAfnnxxReVnp4uj8dTqX348OG69dZbtW3bNg0fPlwpKSlq0qSJunXrpuXLl592fjt37pRhGNq4caO37fDhwzIMQytXrvS2bd68WUOGDFGTJk2UkpKiW265RS6Xq7ZXDwDgI4IFAMAv1157rQoKCvTpp5962w4dOqSlS5fq5ptvVnFxsYYOHaoVK1Zow4YNGjx4sIYNG6a8vDy/l3n48GFdccUVuuiii7R+/XotXbpUBw4c0HXXXVcbqwQAsKCR3QUAAEJTs2bNNGTIEC1evFj9+/eXJL399ttKTExUv3795HA4dMEFF3inf/DBB7VkyRK9//77mjRpkl/LfPbZZ3XRRRfpkUce8ba9/PLLysjI0A8//KD27dtbWykAgN84YgEA8NvNN9+sf/zjHyorK5MkLVq0SDfccIMcDoeKi4s1depUderUSXFxcWrSpIlycnIsHbH49ttv9emnn6pJkybev44dO0qStm3bVivrBADwD0csAAB+GzZsmEzT1Icffqhu3bppzZo1evLJJyVJU6dO1SeffKK//OUvatu2raKiovSb3/xGx44dq3ZeDsfx/+syTdPbVl5eXmma4uJiDRs2TI899liV/mlpabW1WgAAPxAsAAB+czqdGjlypBYtWqStW7eqQ4cOuvjiiyVJn3/+ucaNG6drrrlG0vFQsHPnztPOKykpSZK0b98+XXTRRZJU6UJuSbr44ov1j3/8Q61atVKjRnyFAUAw4VQoAIAlN998sz788EO9/PLLuvnmm73t7dq10zvvvKONGzfq22+/1U033VTlDlIni4qK0mWXXaZHH31UOTk5WrVqlaZPn15pmokTJ+rQoUO68cYb9fXXX2vbtm1atmyZxo8fr4qKioCtIwDg7AgWAABLrrjiCsXHxys3N1c33XSTt/2JJ55Qs2bNdPnll2vYsGEaNGiQ92jG6bz88sv65ZdflJmZqbvvvlsPPfRQpefT09P1+eefq6KiQgMHDtT555+vu+++W3Fxcd5TqQAA9jDMk09mBQAAAAA/8N87AAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAy/4/HojC5rFtZy8AAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "from sklearn.preprocessing import StandardScaler\n", + "from matplotlib import pyplot as plt\n", "# l = 6\n", "# g = 3\n", "# para_dict = {'l':l,'g':g}\n", @@ -94,8 +69,6 @@ "# total_dataset.X = torch.tensor(total_dataset.X)\n", "\n", "\n", - "import random\n", - "from torch.utils.data import Subset\n", "import pandas as pd\n", "def plot_distribution(y, bin_width):\n", " bins = np.arange(np.min(y), np.max(y) + bin_width, bin_width)\n", @@ -164,7 +137,6 @@ " var = posterior.variance\n", "\n", "\n", - "\n", " err = torch.sqrt(var) if use_std else var\n", "\n", "\n", @@ -212,7 +184,7 @@ }, { "cell_type": "markdown", - "id": "8b557acd", + "id": "3", "metadata": {}, "source": [ "# Initialize the Encoding\n", @@ -252,287 +224,10 @@ }, { "cell_type": "code", - "execution_count": 5, - "id": "2d13fe48", + "execution_count": null, + "id": "4", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'decode': {'sizes': {'SZ': 18, 'DQ': 36, 'SX': 18},\n", - " 'adj': {'SZ_DQ': tensor(indices=tensor([[ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3,\n", - " 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6,\n", - " 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10,\n", - " 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13,\n", - " 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17,\n", - " 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23,\n", - " 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30,\n", - " 31, 31, 32, 32, 33, 33, 34, 34, 35, 35],\n", - " [ 0, 3, 9, 15, 1, 4, 10, 16, 2, 5, 11, 17, 0, 3,\n", - " 6, 12, 1, 4, 7, 13, 2, 5, 8, 14, 3, 6, 9, 15,\n", - " 4, 7, 10, 16, 5, 8, 11, 17, 0, 6, 9, 12, 1, 7,\n", - " 10, 13, 2, 8, 11, 14, 3, 9, 12, 15, 4, 10, 13, 16,\n", - " 5, 11, 14, 17, 0, 6, 12, 15, 1, 7, 13, 16, 2, 8,\n", - " 14, 17, 2, 3, 0, 4, 1, 5, 5, 6, 3, 7, 4, 8,\n", - " 8, 9, 6, 10, 7, 11, 11, 12, 9, 13, 10, 14, 14, 15,\n", - " 12, 16, 13, 17, 0, 17, 1, 15, 2, 16]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(36, 18), nnz=108, layout=torch.sparse_coo),\n", - " 'DQ_SZ': tensor(indices=tensor([[ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2,\n", - " 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,\n", - " 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6,\n", - " 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9,\n", - " 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11,\n", - " 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13,\n", - " 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16,\n", - " 16, 16, 16, 16, 17, 17, 17, 17, 17, 17],\n", - " [ 0, 3, 9, 15, 19, 33, 1, 4, 10, 16, 20, 34, 2, 5,\n", - " 11, 17, 18, 35, 0, 3, 6, 12, 18, 22, 1, 4, 7, 13,\n", - " 19, 23, 2, 5, 8, 14, 20, 21, 3, 6, 9, 15, 21, 25,\n", - " 4, 7, 10, 16, 22, 26, 5, 8, 11, 17, 23, 24, 0, 6,\n", - " 9, 12, 24, 28, 1, 7, 10, 13, 25, 29, 2, 8, 11, 14,\n", - " 26, 27, 3, 9, 12, 15, 27, 31, 4, 10, 13, 16, 28, 32,\n", - " 5, 11, 14, 17, 29, 30, 0, 6, 12, 15, 30, 34, 1, 7,\n", - " 13, 16, 31, 35, 2, 8, 14, 17, 32, 33]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(18, 36), nnz=108, layout=torch.sparse_coo),\n", - " 'DQ_SX': tensor(indices=tensor([[ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2,\n", - " 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,\n", - " 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6,\n", - " 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9,\n", - " 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11,\n", - " 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13,\n", - " 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16,\n", - " 16, 16, 16, 16, 17, 17, 17, 17, 17, 17],\n", - " [ 2, 3, 18, 21, 27, 33, 0, 4, 19, 22, 28, 34, 1, 5,\n", - " 20, 23, 29, 35, 5, 6, 18, 21, 24, 30, 3, 7, 19, 22,\n", - " 25, 31, 4, 8, 20, 23, 26, 32, 8, 9, 21, 24, 27, 33,\n", - " 6, 10, 22, 25, 28, 34, 7, 11, 23, 26, 29, 35, 11, 12,\n", - " 18, 24, 27, 30, 9, 13, 19, 25, 28, 31, 10, 14, 20, 26,\n", - " 29, 32, 14, 15, 21, 27, 30, 33, 12, 16, 22, 28, 31, 34,\n", - " 13, 17, 23, 29, 32, 35, 0, 17, 18, 24, 30, 33, 1, 15,\n", - " 19, 25, 31, 34, 2, 16, 20, 26, 32, 35]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(18, 36), nnz=108, layout=torch.sparse_coo),\n", - " 'SX_DQ': tensor(indices=tensor([[ 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,\n", - " 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13,\n", - " 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 18, 18, 19, 19,\n", - " 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22,\n", - " 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26,\n", - " 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29,\n", - " 30, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33,\n", - " 33, 33, 34, 34, 34, 34, 35, 35, 35, 35],\n", - " [ 1, 15, 2, 16, 0, 17, 0, 4, 1, 5, 2, 3, 3, 7,\n", - " 4, 8, 5, 6, 6, 10, 7, 11, 8, 9, 9, 13, 10, 14,\n", - " 11, 12, 12, 16, 13, 17, 14, 15, 0, 3, 9, 15, 1, 4,\n", - " 10, 16, 2, 5, 11, 17, 0, 3, 6, 12, 1, 4, 7, 13,\n", - " 2, 5, 8, 14, 3, 6, 9, 15, 4, 7, 10, 16, 5, 8,\n", - " 11, 17, 0, 6, 9, 12, 1, 7, 10, 13, 2, 8, 11, 14,\n", - " 3, 9, 12, 15, 4, 10, 13, 16, 5, 11, 14, 17, 0, 6,\n", - " 12, 15, 1, 7, 13, 16, 2, 8, 14, 17]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(36, 18), nnz=108, layout=torch.sparse_coo)},\n", - " 'deg': {'SZ_DQ': (tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.]),\n", - " tensor([4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.,\n", - " 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.])),\n", - " 'DQ_SZ': (tensor([4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.,\n", - " 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.]),\n", - " tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.])),\n", - " 'DQ_SX': (tensor([2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.,\n", - " 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.]),\n", - " tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.])),\n", - " 'SX_DQ': (tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.]),\n", - " tensor([2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.,\n", - " 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.]))}},\n", - " 'xlogic': {'sizes': {'DQ': 36, 'SX': 18, 'LX': 2},\n", - " 'adj': {'DQ_SX': tensor(indices=tensor([[ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2,\n", - " 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,\n", - " 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6,\n", - " 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9,\n", - " 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11,\n", - " 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13,\n", - " 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16,\n", - " 16, 16, 16, 16, 17, 17, 17, 17, 17, 17],\n", - " [ 2, 3, 18, 21, 27, 33, 0, 4, 19, 22, 28, 34, 1, 5,\n", - " 20, 23, 29, 35, 5, 6, 18, 21, 24, 30, 3, 7, 19, 22,\n", - " 25, 31, 4, 8, 20, 23, 26, 32, 8, 9, 21, 24, 27, 33,\n", - " 6, 10, 22, 25, 28, 34, 7, 11, 23, 26, 29, 35, 11, 12,\n", - " 18, 24, 27, 30, 9, 13, 19, 25, 28, 31, 10, 14, 20, 26,\n", - " 29, 32, 14, 15, 21, 27, 30, 33, 12, 16, 22, 28, 31, 34,\n", - " 13, 17, 23, 29, 32, 35, 0, 17, 18, 24, 30, 33, 1, 15,\n", - " 19, 25, 31, 34, 2, 16, 20, 26, 32, 35]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(18, 36), nnz=108, layout=torch.sparse_coo),\n", - " 'SX_DQ': tensor(indices=tensor([[ 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,\n", - " 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13,\n", - " 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 18, 18, 19, 19,\n", - " 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22,\n", - " 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26,\n", - " 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29,\n", - " 30, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33,\n", - " 33, 33, 34, 34, 34, 34, 35, 35, 35, 35],\n", - " [ 1, 15, 2, 16, 0, 17, 0, 4, 1, 5, 2, 3, 3, 7,\n", - " 4, 8, 5, 6, 6, 10, 7, 11, 8, 9, 9, 13, 10, 14,\n", - " 11, 12, 12, 16, 13, 17, 14, 15, 0, 3, 9, 15, 1, 4,\n", - " 10, 16, 2, 5, 11, 17, 0, 3, 6, 12, 1, 4, 7, 13,\n", - " 2, 5, 8, 14, 3, 6, 9, 15, 4, 7, 10, 16, 5, 8,\n", - " 11, 17, 0, 6, 9, 12, 1, 7, 10, 13, 2, 8, 11, 14,\n", - " 3, 9, 12, 15, 4, 10, 13, 16, 5, 11, 14, 17, 0, 6,\n", - " 12, 15, 1, 7, 13, 16, 2, 8, 14, 17]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(36, 18), nnz=108, layout=torch.sparse_coo),\n", - " 'LX_DQ': tensor(indices=tensor([[ 2, 3, 4, 5, 5, 6, 7, 8, 8, 11, 12, 13, 14, 14,\n", - " 17, 18, 19, 20],\n", - " [ 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1,\n", - " 0, 1, 1, 1]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1.]),\n", - " size=(36, 2), nnz=18, layout=torch.sparse_coo),\n", - " 'DQ_LX': tensor(indices=tensor([[ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,\n", - " 1, 1, 1, 1],\n", - " [ 2, 5, 8, 11, 14, 17, 3, 4, 5, 6, 7, 8, 12, 13,\n", - " 14, 18, 19, 20]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1.]),\n", - " size=(2, 36), nnz=18, layout=torch.sparse_coo)},\n", - " 'deg': {'DQ_SX': (tensor([2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.,\n", - " 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.]),\n", - " tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.])),\n", - " 'SX_DQ': (tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.]),\n", - " tensor([2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.,\n", - " 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.])),\n", - " 'LX_DQ': (tensor([ 6., 12.]),\n", - " tensor([0., 0., 1., 1., 1., 2., 1., 1., 2., 0., 0., 1., 1., 1., 2., 0., 0., 1.,\n", - " 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])),\n", - " 'DQ_LX': (tensor([0., 0., 1., 1., 1., 2., 1., 1., 2., 0., 0., 1., 1., 1., 2., 0., 0., 1.,\n", - " 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]),\n", - " tensor([ 6., 12.]))}},\n", - " 'zlogic': {'sizes': {'SZ': 18, 'DQ': 36, 'LZ': 2},\n", - " 'adj': {'SZ_DQ': tensor(indices=tensor([[ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3,\n", - " 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6,\n", - " 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10,\n", - " 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13,\n", - " 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17,\n", - " 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23,\n", - " 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30,\n", - " 31, 31, 32, 32, 33, 33, 34, 34, 35, 35],\n", - " [ 0, 3, 9, 15, 1, 4, 10, 16, 2, 5, 11, 17, 0, 3,\n", - " 6, 12, 1, 4, 7, 13, 2, 5, 8, 14, 3, 6, 9, 15,\n", - " 4, 7, 10, 16, 5, 8, 11, 17, 0, 6, 9, 12, 1, 7,\n", - " 10, 13, 2, 8, 11, 14, 3, 9, 12, 15, 4, 10, 13, 16,\n", - " 5, 11, 14, 17, 0, 6, 12, 15, 1, 7, 13, 16, 2, 8,\n", - " 14, 17, 2, 3, 0, 4, 1, 5, 5, 6, 3, 7, 4, 8,\n", - " 8, 9, 6, 10, 7, 11, 11, 12, 9, 13, 10, 14, 14, 15,\n", - " 12, 16, 13, 17, 0, 17, 1, 15, 2, 16]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(36, 18), nnz=108, layout=torch.sparse_coo),\n", - " 'DQ_SZ': tensor(indices=tensor([[ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2,\n", - " 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,\n", - " 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6,\n", - " 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9,\n", - " 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11,\n", - " 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13,\n", - " 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16,\n", - " 16, 16, 16, 16, 17, 17, 17, 17, 17, 17],\n", - " [ 0, 3, 9, 15, 19, 33, 1, 4, 10, 16, 20, 34, 2, 5,\n", - " 11, 17, 18, 35, 0, 3, 6, 12, 18, 22, 1, 4, 7, 13,\n", - " 19, 23, 2, 5, 8, 14, 20, 21, 3, 6, 9, 15, 21, 25,\n", - " 4, 7, 10, 16, 22, 26, 5, 8, 11, 17, 23, 24, 0, 6,\n", - " 9, 12, 24, 28, 1, 7, 10, 13, 25, 29, 2, 8, 11, 14,\n", - " 26, 27, 3, 9, 12, 15, 27, 31, 4, 10, 13, 16, 28, 32,\n", - " 5, 11, 14, 17, 29, 30, 0, 6, 12, 15, 30, 34, 1, 7,\n", - " 13, 16, 31, 35, 2, 8, 14, 17, 32, 33]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),\n", - " size=(18, 36), nnz=108, layout=torch.sparse_coo),\n", - " 'LZ_DQ': tensor(indices=tensor([[ 0, 0, 1, 2, 4, 6, 7, 8, 8, 9, 9, 10, 11, 13,\n", - " 17, 18, 19, 20],\n", - " [ 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0,\n", - " 0, 1, 1, 1]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1.]),\n", - " size=(36, 2), nnz=18, layout=torch.sparse_coo),\n", - " 'DQ_LZ': tensor(indices=tensor([[ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,\n", - " 1, 1, 1, 1],\n", - " [ 0, 4, 8, 9, 13, 17, 0, 1, 2, 6, 7, 8, 9, 10,\n", - " 11, 18, 19, 20]]),\n", - " values=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", - " 1., 1., 1., 1.]),\n", - " size=(2, 36), nnz=18, layout=torch.sparse_coo)},\n", - " 'deg': {'SZ_DQ': (tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.]),\n", - " tensor([4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.,\n", - " 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.])),\n", - " 'DQ_SZ': (tensor([4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4., 4.,\n", - " 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.]),\n", - " tensor([6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.])),\n", - " 'LZ_DQ': (tensor([ 6., 12.]),\n", - " tensor([2., 1., 1., 0., 1., 0., 1., 1., 2., 2., 1., 1., 0., 1., 0., 0., 0., 1.,\n", - " 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])),\n", - " 'DQ_LZ': (tensor([2., 1., 1., 0., 1., 0., 1., 1., 2., 2., 1., 1., 0., 1., 0., 0., 0., 1.,\n", - " 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]),\n", - " tensor([ 6., 12.]))}}}" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "views_info = [\n", " {\"name\":\"decode\",\n", @@ -627,8 +322,8 @@ }, { "cell_type": "code", - "execution_count": 6, - "id": "6a6351c4", + "execution_count": null, + "id": "5", "metadata": {}, "outputs": [], "source": [ @@ -636,7 +331,7 @@ " test_size=0.2,\n", " shuffle=True,\n", " random_state=seed)\n", - "from torch import nn, optim\n", + "from torch import optim\n", "\n", "def train_gpytorch_model(model, train_x, train_y, training_iter=50, lr=0.002):\n", " model.train()\n", @@ -654,6 +349,7 @@ " history.append(loss.item())\n", " plt.plot(history)\n", " return loss.item()\n", + "\n", "def train_gpytorch_model_seperate(model, train_x, train_y, training_iter=50, lr=0.002):\n", " model.train()\n", " model.likelihood.train()\n", @@ -693,7 +389,7 @@ }, { "cell_type": "markdown", - "id": "9ac6c7c0", + "id": "6", "metadata": {}, "source": [ "# Results\n", @@ -806,7 +502,7 @@ }, { "cell_type": "markdown", - "id": "cd46b583", + "id": "7", "metadata": {}, "source": [ "# Test our embedding on nn-regressor" @@ -814,274 +510,16 @@ }, { "cell_type": "code", - "execution_count": 10, - "id": "afe8a4e6", + "execution_count": null, + "id": "8", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Epoch 1 | Loss: 0.318859\n", - "Epoch 2 | Loss: 0.236694\n", - "Epoch 3 | Loss: 0.217672\n", - "Epoch 4 | Loss: 0.210474\n", - "Epoch 5 | Loss: 0.199423\n", - "Epoch 6 | Loss: 0.214220\n", - "Epoch 7 | Loss: 0.199688\n", - "Epoch 8 | Loss: 0.203249\n", - "Epoch 9 | Loss: 0.187467\n", - "Epoch 10 | Loss: 0.190376\n", - "Epoch 11 | Loss: 0.194188\n", - "Epoch 12 | Loss: 0.183740\n", - "Epoch 13 | Loss: 0.186241\n", - "Epoch 14 | Loss: 0.184350\n", - "Epoch 15 | Loss: 0.174657\n", - "Epoch 16 | Loss: 0.183481\n", - "Epoch 17 | Loss: 0.176370\n", - "Epoch 18 | Loss: 0.172502\n", - "Epoch 19 | Loss: 0.160232\n", - "Epoch 20 | Loss: 0.163823\n", - "Epoch 21 | Loss: 0.154833\n", - "Epoch 22 | Loss: 0.162528\n", - "Epoch 23 | Loss: 0.149254\n", - "Epoch 24 | Loss: 0.143769\n", - "Epoch 25 | Loss: 0.172772\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAHqCAYAAAAZLi26AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAADmFklEQVR4nOzdd3hUVfrA8e+09IQkpFGSEHrvgohSlC4q9rIuYN+1obhr2137T3bXsrg2dF0Fe127Ik1EBUF6kU4gQEgjvU9m7u+PkzuTnplkJjOB9/M8PHdy55YzuZNh3nvOeV+DpmkaQgghhBBCCCGE8DijrxsghBBCCCGEEEKcqiToFkIIIYQQQgghvESCbiGEEEIIIYQQwksk6BZCCCGEEEIIIbxEgm4hhBBCCCGEEMJLJOgWQgghhBBCCCG8RIJuIYQQQgghhBDCSyToFkIIIYQQQgghvESCbiGEEEIIIYQQwksk6BZCCOFT3bp1Y+7cuY6fV69ejcFgYPXq1R47h8Fg4JFHHvHY8YR7jh49SlBQED///LOvm+Ixrr5PH3nkEQwGAzk5OW3TMD+waNEikpKSqKio8HVThBDCL0jQLYQQp7HFixdjMBgc/4KCgujduze33347mZmZvm6eW7755pvTJrDWAz5X/vmDxx57jNGjRzN27Ng2P/eTTz7JZ5991ubnbSt1/4Yb+9etW7c2a9PcuXOprKzklVdeabNzCiGEPzP7ugFCCCF877HHHiMlJYXy8nJ++uknXn75Zb755ht27txJSEhIm7Zl3LhxlJWVERAQ4NZ+33zzDS+++GKDgXdZWRlm86nzX16/fv146623aq174IEHCAsL4y9/+YuPWtWw7OxslixZwpIlS3xy/ieffJLLLruMWbNm+eT83jZu3Lh674Ubb7yRUaNGcfPNNzvWhYWFtVmbgoKCmDNnDs8++yx33HGH39z8EUIIXzl1voEIIYRosenTpzNy5EhAfWHv2LEjzz77LJ9//jlXX311g/uUlJQQGhrq8bYYjUaCgoI8ekxPH8/X4uPjufbaa2ut+/vf/05MTEy99TXZ7XYqKyvb9Pfx9ttvYzabueCCC9rsnJqmUV5eTnBwcJud01e6d+9O9+7da637wx/+QPfu3Zt8L1RVVWG3292+ueWqK664gn/+8598//33nHvuuV45hxBCtBcyvFwIIUQ9+pfk1NRUQA0XDQsL4+DBg8yYMYPw8HB+97vfASqQW7hwIQMGDCAoKIj4+HhuueUW8vLyah1T0zSeeOIJunbtSkhICBMnTmTXrl31zt3YXNn169czY8YMoqKiCA0NZfDgwTz33HOO9r344osADQ6tbmhO95YtW5g+fToRERGEhYVx3nnn8csvv9TaRh+6+/PPPzN//nxiY2MJDQ3l4osvJjs7u8nf4dNPP43BYODIkSP1nnvggQcICAhw/I7279/PpZdeSkJCAkFBQXTt2pWrrrqKgoKCJs/RHIPBwO23384777zDgAEDCAwMZOnSpY3+jg8fPozBYGDx4sW11u/Zs4fLLruM6OhogoKCGDlyJF988YVLbfjss88YPXp0vZ5WV15zVVUVjz/+OD169CAwMJBu3brx4IMP1psr3K1bN2bOnMl3333HyJEjCQ4O5pVXXsFgMFBSUsKSJUsc74ma+QOOHz/O9ddfT3x8PIGBgQwYMIDXX3+93ms4duwYs2bNIjQ0lLi4OO6++2635yvn5ORwxRVXEBERQceOHZk3bx7l5eWO58ePH8+QIUMa3LdPnz5MnTrVrfPVpF/Xp59+moULFzp+n7/99pvjPX748OFa+zT1dzht2jQ6dOhASEgI48ePb3Cu/ogRI4iOjubzzz9vcbuFEOJUIT3dQggh6jl48CAAHTt2dKyrqqpi6tSpnH322Tz99NOOYee33HILixcv5rrrruPOO+8kNTWVF154gS1btvDzzz9jsVgAeOihh3jiiSeYMWMGM2bMYPPmzUyZMoXKyspm27N8+XJmzpxJp06dmDdvHgkJCezevZuvvvqKefPmccstt5Cens7y5cvrDbVtyK5duzjnnHOIiIjg3nvvxWKx8MorrzBhwgR++OEHRo8eXWv7O+64g6ioKB5++GEOHz7MwoULuf322/nggw8aPccVV1zBvffey4cffsif//znWs99+OGHTJkyhaioKCorK5k6dSoVFRXccccdJCQkcPz4cb766ivy8/Pp0KFDs6+nKatWreLDDz/k9ttvJyYmhm7dupGfn+/y/rt27WLs2LF06dKF+++/n9DQUD788ENmzZrFJ598wsUXX9zovlarlV9//ZU//vGPtda7+ppvvPFGlixZwmWXXcY999zD+vXrWbBgAbt37+bTTz+tdcy9e/dy9dVXc8stt3DTTTfRp08f3nrrrXpDrXv06AFAZmYmZ555puPGRGxsLN9++y033HADhYWF3HXXXYCamnDeeeeRlpbGnXfeSefOnXnrrbdYtWqVy79DUO+Hbt26sWDBAn755Rf+/e9/k5eXx5tvvgnA73//e2666SZ27tzJwIEDHfv9+uuv7Nu3j7/+9a9una8hb7zxBuXl5dx8880EBgYSHR3t1v6rVq1i+vTpjBgxgocffhij0cgbb7zBueeey48//sioUaNqbT98+PBTKnmeEEK0mCaEEOK09cYbb2iAtmLFCi07O1s7evSo9v7772sdO3bUgoODtWPHjmmapmlz5szRAO3++++vtf+PP/6oAdo777xTa/3SpUtrrc/KytICAgK0888/X7Pb7Y7tHnzwQQ3Q5syZ41j3/fffa4D2/fffa5qmaVVVVVpKSoqWnJys5eXl1TpPzWPddtttWmP/rQHaww8/7Ph51qxZWkBAgHbw4EHHuvT0dC08PFwbN25cvd/PpEmTap3r7rvv1kwmk5afn9/g+XRjxozRRowYUWvdhg0bNEB78803NU3TtC1btmiA9tFHHzV5rOYMGDBAGz9+fK11gGY0GrVdu3bVWl/3d6xLTU3VAO2NN95wrDvvvPO0QYMGaeXl5Y51drtdO+uss7RevXo12aYDBw5ogPb888/XWu/Ka966dasGaDfeeGOt9X/60580QFu1apVjXXJysgZoS5curXec0NDQWu8v3Q033KB16tRJy8nJqbX+qquu0jp06KCVlpZqmqZpCxcu1ADtww8/dGxTUlKi9ezZs8HfYV0PP/ywBmgXXnhhrfW33nqrBmjbtm3TNE3T8vPztaCgIO2+++6rtd2dd96phYaGasXFxU2ep6a6r1m/rhEREVpWVlatbfX3eGpqaq31dd8jdrtd69WrlzZ16tRafwulpaVaSkqKNnny5HrtuPnmm7Xg4GCX2y2EEKcqGV4uhBCCSZMmERsbS2JiIldddRVhYWF8+umndOnSpdZ2dXssP/roIzp06MDkyZPJyclx/BsxYgRhYWF8//33AKxYsYLKysp6SZX03sSmbNmyhdTUVO666y4iIyNrPdeSBE02m41ly5Yxa9asWnNhO3XqxDXXXMNPP/1EYWFhrX1uvvnmWuc655xzsNlsDQ4dr+nKK69k06ZNjpEDAB988AGBgYFcdNFFAI5e3e+++47S0lK3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lqNknqtSU1O58MILmTZtGuvWrePtt9/mmmuuqVebe9iwYQwcOJCPPvqIfv36MXz4cLfO464BAwZw5pln8sADD5Cbm0t0dDTvv/9+vZsiRqOR1157jenTpzNgwACuu+46unTpwvHjx/n++++JiIjgyy+/dGy/adMmcnNzHQkDhRDitOajrOlCCCH8gF4u6Ndff21yuzlz5mihoaGNPv/qq69qI0aM0IKDg7Xw8HBt0KBB2r333qulp6c7trHZbNqjjz6qderUSQsODtYmTJig7dy5U0tOTm6yZJjup59+0iZPnqyFh4droaGh2uDBg2uVoqqqqtLuuOMOLTY2VjMYDLXKh1GnZJimadrmzZu1qVOnamFhYVpISIg2ceJEbe3atS79fhprY2P+85//aIAWHh6ulZWV1Xru0KFD2vXXX6/16NFDCwoK0qKjo7WJEydqK1ascOnYusZKht12220Nbp+dna1deumlWkhIiBYVFaXdcsst2s6dO+uVDNM0TTt48KA2e/ZsLSEhQbNYLFqXLl20mTNnah9//HGz7crMzNTMZrP21ltvuf2arVar9uijj2opKSmaxWLREhMTtQceeKBW+TJNUyXDzj///AbPv2fPHm3cuHFacHBwvfJ0mZmZ2m233aYlJiZqFotFS0hI0M477zzt1VdfrXWMI0eOaBdeeKEWEhKixcTEaPPmzXOUxXO1ZNhvv/2mXXbZZVp4eLgWFRWl3X777fXeC7p//vOfGqA9+eSTTR67MY2VDHvqqaca3P7gwYPapEmTtMDAQC0+Pl578MEHteXLlzf4+rZs2aJdcsklWseOHbXAwEAtOTlZu+KKK7SVK1fW2u6+++7TkpKSapUXE0KI05VB05rJAiOEEEII0Qo33HAD+/btc2TlFk177rnnuPvuuzl8+HC9zPntQUVFBd26deP+++9n3rx5vm6OEEL4nATdQgghhPCqtLQ0evfuzcqVKxk7dqyvm+PXNE1jyJAhdOzY0VHnvr1ZtGgRTz75JPv37ycwMNDXzRFCCJ+ToFsIIYQQwsdKSkr44osv+P777/nPf/7D559/zoUXXujrZgkhhPAACbqFEEIIIXzs8OHDpKSkEBkZya233sr//d//+bpJQgghPESCbiGEEEIIIYQQwkukTrcQQgghhBBCCOElEnQLIYQQQgghhBBeYvZ1A/yd3W4nPT2d8PBwDAaDr5sjhBBCCCGEEMIPaJpGUVERnTt3xmhsvD9bgu5mpKenk5iY6OtmCCGEEEIIIYTwQ0ePHqVr166NPi9BdzPCw8MB9YuMiIjwcWsaZrVaWbZsGVOmTMFisfi6OaIBco38m1wf/ybXx//JNfJvcn38m1wf/yfXyL/58voUFhaSmJjoiBkbI0F3M/Qh5REREX4ddIeEhBARESEfBH5KrpF/k+vj3+T6+D+5Rv5Nro9/k+vj/+Qa+Td/uD7NTUOWRGpCCCGEEEIIIYSXSNAthBBCCCGEEEJ4iQTdQgghhBBCCCGEl8icbg+x2WxYrVafnNtqtWI2mykvL8dms/mkDe2ZxWLBZDL5uhlCCCGEEEKIU5AE3a2kaRoZGRnk5+f7tA0JCQkcPXpUaom3UGRkJAkJCfL7E0IIIYQQQniUBN2tpAfccXFxhISE+CRos9vtFBcXExYW1mRRdlGfpmmUlpaSlZUFQKdOnXzcIiGEEEIIIcSpRILuVrDZbI6Au2PHjj5rh91up7KykqCgIAm6WyA4OBiArKws4uLiZKi5EEIIIYQQwmMkQmsFfQ53SEiIj1siWku/hr6aly+EEEIIIYQ4NUnQ7QEyD7j9k2sohBBCCCGE8AYJuoUQQgghhBBCCC9pN0H3ggULOOOMMwgPDycuLo5Zs2axd+/eZvf76KOP6Nu3L0FBQQwaNIhvvvmmDVorhBBCCCGEEEK0o6D7hx9+4LbbbuOXX35h+fLlWK1WpkyZQklJSaP7rF27lquvvpobbriBLVu2MGvWLGbNmsXOnTvbsOX+xWAwNPnvkUce8XUThRBCCCGEEOKU0W6yly9durTWz4sXLyYuLo5NmzYxbty4Bvd57rnnmDZtGn/+858BePzxx1m+fDkvvPACixYt8nqb/dGJEyccjz/44AMeeuihWiMGwsLCHI81TcNms2E2t5u3iRBCCCGEEEL4lXYbTRUUFAAQHR3d6Dbr1q1j/vz5tdZNnTqVzz77rNF9KioqqKiocPxcWFgIqKzWdTNbW61WNE3Dbrdjt9vdfQkeo2maY9lcO+Li4hyPw8PDMRgMjnWrV6/mvPPO46uvvuKhhx5ix44dLF26lCVLlpCfn8+nn37q2Pfuu+9m27ZtrFq1ClBly/75z3/yn//8h4yMDHr37s1f/vIXLrvsMk+/XK+w2+1omobVavVKyTD9vSPZ0f2TXB//JtfH/8k18m9yffybXB//J9fIv/ny+rh6znYZdNvtdu666y7Gjh3LwIEDG90uIyOD+Pj4Wuvi4+PJyMhodJ8FCxbw6KOP1lu/bNmyeqXBzGYzCQkJFBcXU1lZiaZplFt9E3wHWYwUFRW5tU95eTmapjluLJSWlgJw33338fjjj9OtWzciIyOxWq1UVVU5tgOorKyste7pp5/mo48+4umnn6ZHjx6sXbuW2bNnExoaytixYz30Kr2nsrKSsrIy1qxZQ1VVldfOs3z5cq8dW7SeXB//JtfH/8k18m9yffybXB//J9eoba04bmB4jEZ0oGvb++L66PFTc9pl0H3bbbexc+dOfvrpJ48f+4EHHqjVO15YWEhiYiJTpkwhIiKi1rbl5eUcPXqUsLAwgoKCKK2sYtg/fPPHuG7+mcR1jHSr9FVQUBAGg8HxuvSbCo8//jgXXXSRYzuLxYLZbK71+gMCAhzrKioq+Ne//sWyZcsYM2YMAIMHD2bTpk28/fbbTJ8+3RMv0avKy8sJDg5m3LhxBAUFefz4VquV5cuXM3nyZCwWi8ePL1pHro9/k+vj/+Qa+Te5Pv5Nro//k2vU9j7dks6X63by80kLK+46h/CgxsNWX16fmp2STWl3Qfftt9/OV199xZo1a+jatWuT2yYkJJCZmVlrXWZmJgkJCY3uExgYSGBg/dspFoul3kW02WwYDAaMRqPjny/pbXGVvm3d5ahRo2odR0+yVnedvs+hQ4coLS1l6tSptY5fWVnJsGHDfP57cYXRaMRgMDR4nT3J28cXrSPXx7/J9fF/co38m1wf/ybXx//JNWobh7KLeeSr3QBcd1YK0eHBLu3ni+vj6vnaTdCtaRp33HEHn376KatXryYlJaXZfcaMGcPKlSu56667HOuWL1/u6I31tGCLid8em9r8hh5mt9uxljWexd1doaGhtX42Go2OeeO6mvMXiouLAfj666/p0qVLre0auoEhhBBCCCGEEHVVVNm4470tlFbaGNO9I7dO7OnrJnlEuwm6b7vtNt59910+//xzwsPDHfOyO3ToQHCwuvsxe/ZsunTpwoIFCwCYN28e48eP55lnnuH888/n/fffZ+PGjbz66qteaaPBYCAkoO1/pXa7ncJy14eVuys2NrZembWtW7c67uz079+fwMBA0tLSGD9+vNfaIYQQQgghhDh1/f3bPexKLyQ6NICFVw3FZPRejNOW/H/cb7WXX36ZgoICJkyYQKdOnRz/PvjgA8c2aWlptUpinXXWWbz77ru8+uqrDBkyhI8//pjPPvusyeRror5zzz2XjRs38uabb7J//34efvjhWkF4eHg4f/rTn7j77rtZsmQJBw8eZPPmzTz//PMsWbLEhy0XQgghhBBCtAcrd2fyxs+HAXj68sHER3g+z5KvtJue7rrDmxuyevXqeusuv/xyLr/8ci+06PQxdepU/va3v3HvvfdSXl7O9ddfz+zZs9mxY4djm8cff5zY2FgWLFjAoUOHiIyMZPjw4Tz44IM+bLkQQgghhBDC32UUlPOnj7YBcP3YFM7tG9/MHu1Luwm6hefNnTuXuXPnOn6eMGFCozc3Hn300QZLqekMBgPz5s1j3rx5nm6mEEIIIYQQ4hRls2vMe38LeaVWBnSO4L7pfcBuh5y9ENsX3KjO5K/azfByIYQQQgghhBCnlhe/P8D61FxCAkw8f/UwAs0myNwJL50J/x4GLox49ncSdAshhBBCCCGEaHO/Hs5l4Yp9ADx+0UC6x4apJ1LXqGXHntLTLYQQQgghhBBCuCu/tJJ5723BrsElw7pw6Yiuzif1oLv7qVEZSYJuIYQQQgghhBBtRtM07vtkO+kF5XTrGMJjs2pUl7JZ4cjP6nHKON800MMk6BZCCCGEEEII0WbeXp/Gd7sysZgMPH/1cMICa+T3Tt8ClcUQFAnxg3zWRk+SoFsIIYQQQgghRJvYfaKQx7/6DYD7pvVlUNcOtTdI/UEtU84B46kRrp4ar0IIIYQQQgghhF8rrazijve2UFllZ2KfWG44O6X+Rvp87pRTYz43SJ1uIYQQQgghhBAeUFpZRXp+OScKykjPLyM9v1wtC8o4kV/O8fwyKqrsxIUH8vTlQzDUzUxuLYe09erxKTKfGyToFkIIIYQQQgjRQjnFFdz/yXY2Hckjr9Ta7PaRIRb+ffUwOoYF1n/y2AawVUBYAsT09kJrfUOCbuFVc+fOJT8/n88++wyACRMmMHToUBYuXNim7Vi9ejUTJ04kLy+PyMjINj23EEIIIYQQp6KjuaXMfn0DqTkljnWhASa6RAXTqUMwnSOD6dwhSC0jg+kcGURChyACzaaGD+gYWj7ulKjPrZOg+zQ1d+5clixZAoDFYiEpKYnZs2fz4IMPYjZ7723xv//9D4vF4tK2EigLIYQQQgjhn/ZlFvH7/64ns7CCLpHB/PvqofSMCyciyFx/2LirDulJ1E6doeUgQfdpbdq0abzxxhtUVFTwzTffcNttt2GxWHjggQdqbVdZWUlAQIBHzhkdHe2R4wghhBBCCCF8Y9ORPK5f/CsFZVZ6x4fx5vWjSegQ1LqDVhTB8U3q8SkWdEv28tNYYGAgCQkJJCcn88c//pFJkybxxRdfMHfuXGbNmsX//d//0blzZ/r06QPA0aNHueKKK4iMjCQ6OpqLLrqIw4cPO45ns9mYP38+kZGRdOzYkXvvvRdN02qdc8KECdx1112OnysqKrjvvvtITEwkMDCQnj178t///pfDhw8zceJEAKKiojAYDMydOxcAu93OggULSElJITg4mCFDhvDxxx/XOs8333xD7969CQ4OZuLEibXaKYQQQgghhGiZH/Zlc+1r6ykoszIsKZIPbxnT+oAb4Mg60GwQmQxRya0/nh+Rnm5P0jSwlrb9ee12de5WCg4O5uTJkwCsXLmSiIgIli9fDoDVamXq1KmMGTOGH3/8EbPZzBNPPMG0adPYvn07AQEBPPPMMyxevJjXX3+dfv368cwzz/Dpp59y7rnnNnrO2bNns27dOv79738zZMgQUlNTycnJITExkU8++YRLL72UvXv3EhERQXBwMAALFizg7bffZtGiRfTq1Ys1a9Zw7bXXEhsby/jx4zl69CiXXHIJt912GzfffDMbN27knnvuafXvRwghhBBCiNPZF9vSuefDrVhtGuN7x/LytcMJCfBQSJl6ag4tBwm6PctaCk92bvPTGgFu2w10aGbLhmmaxsqVK/nuu++44447yM7OJjQ0lNdee80xrPztt9/Gbrfz2muvOeZovPHGG0RGRrJ69WqmTJnCwoULeeCBB7jkkksAWLRoEd99912j5923bx8ffvghy5cvZ9KkSQB0797d8bw+FD0uLs4xp7uiooInn3ySFStWMGbMGMc+P/30E6+88grjx4/n5ZdfpkePHjzzzDMA9OnThx07dvCPf/yjRb8fIYQQQgghTndvrTvMQ1/sQtPggiGdeebyIQSYPThw+hSsz62ToPs09tVXXxEWFobVasVut3PNNdfwyCOPcNtttzFo0KBa87i3bdvGgQMHCA8Pr3WM8vJyDh48SEFBASdOnGD06NGO58xm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r9G4cWPjpptuMjIyMpyvq208w/fff2+0a9fO8PPzM1JSUoz333+/wuv6rOBeM2bMMIBKf+d6D7lXbm6u8fDDDxsJCQmGv7+/0axZM+P//u//jOLiYuc+nvz+sRiGYbili11ERERERETkAqc53SIiIiIiIiIuoqRbRERERERExEWUdIuIiIiIiIi4iJJuERERERERERdR0i0iIiIiIiLiIkq6RURERERERFxESbeIiIiIiIiIiyjpFhEREREREXERJd0iIiIezGKx8M0337g7DJ599lk6derk8uscPnyYqKgoduzY4fJr1ZTExETeeOMNAEpKSkhMTGT58uXuDUpERDyGkm4REanXDh48yH333UdCQgJ+fn7ExMSQmprKokWL3B1ajdixYwcWi4XVq1e7O5Qqeemllxg6dCiJiYkuvc6JiXJN8vX15c9//jNPPPFEjZ9bRETqJm93ByAiIuJOw4YNo6SkhClTptCsWTP279/P7NmzOXz4sLtDq3cKCgr48MMPmTFjhsuuUVJSgq+vr8vODzB8+HAef/xxNmzYQNu2bV16LRER8Xzq6RYRkXorOzubBQsWMGHCBC699FKaNm1Kjx49ePLJJ7nmmmuc+73++uu0b9+eoKAg4uPjuf/++zl27Jjz9cmTJxMWFsYPP/xAq1atCAwM5IYbbqCgoIApU6aQmJhIw4YNeeihh7Db7c7jEhMTeeGFF7jlllsICgqicePGvP3222eMeffu3fzhD38gLCyM8PBwhg4dek5DsefNm4fFYmH27Nl069aNwMBAevfuTXp6eoX9/va3vxEdHU1ISAijR4+mqKjolHN98MEHtG7dGn9/f1JSUvjXv/7lfG3UqFF06NCB4uJiwEx2O3fuzB133HHa2KZPn46fnx8XXXSRc9vRo0cZPnw4kZGRBAQE0KJFCyZNmuR8fd26dVx22WUEBATQqFEj7rnnngptM3LkSK699lpeeukl4uLiaNWqFf3792fnzp08+uijWCwWLBaLc/+FCxdy8cUXExAQQHx8PA899BD5+fnO1w8cOMCQIUMICAggKSmJzz777JSfo2HDhvTp04dp06ad9mcVEZH6Q0m3iIjUW8HBwQQHB/PNN984k8PKWK1WJk6cyIYNG5gyZQpz5sxhzJgxFfYpKChg4sSJTJs2jZ9//pl58+Zx3XXXMX36dKZPn84nn3zCe++9x5dfflnhuL///e907NiRVatWMXbsWB5++GFmzpxZaRw2m43U1FRCQkJYsGABixYtIjg4mEGDBlFSUnJOP/v//d//8dprr7F8+XK8vb0ZNWqU87XPP/+cZ599lpdffpnly5cTGxtbIaEG+Oyzz3j66ad56aWXSEtL4+WXX2bcuHFMmTIFgIkTJ5Kfn8/YsWOd18vOzuaf//znaWNasGABXbt2rbBt3LhxbNy4kZ9++om0tDTeeecdIiIiAMjPzyc1NZWGDRuybNkyvvjiC2bNmsWDDz5Y4RyzZ88mPT2dmTNn8sMPP/DVV1/RpEkTnn/+efbt28e+ffsA2Lp1K4MGDWLYsGGsXbuW//73vyxcuLDC+UaOHMnu3buZO3cuX375Jf/61784cODAKT9Ljx49WLBgwVnbQURE6gFDRESkHvvyyy+Nhg0bGv7+/kbv3r2NJ5980lizZs0Zj/niiy+MRo0aOZ9PmjTJAIyMjAzntnvvvdcIDAw08vLynNtSU1ONe++91/m8adOmxqBBgyqc+6abbjIGDx7sfA4YX3/9tWEYhvHJJ58YrVq1MhwOh/P14uJiIyAgwJgxY0alsW7fvt0AjFWrVhmGYRhz5841AGPWrFnOfX788UcDMAoLCw3DMIxevXoZ999/f4Xz9OzZ0+jYsaPzeXJysjF16tQK+7zwwgtGr169nM9/++03w8fHxxg3bpzh7e1tLFiwoNIYyw0dOtQYNWpUhW1Dhgwx7rzzzkr3f//9942GDRsax44dq/CzWK1WIysryzAMwxgxYoQRHR1tFBcXVzi2adOmxj/+8Y8K20aPHm3cc889FbYtWLDAsFqtRmFhoZGenm4AxtKlS52vp6WlGcAp53rzzTeNxMTEM/68IiJSP6inW0RE6rVhw4axd+9evvvuOwYNGsS8efPo0qULkydPdu4za9YsBgwYQOPGjQkJCeH222/n8OHDFBQUOPcJDAwkOTnZ+Tw6OprExESCg4MrbDu5V7RXr16nPE9LS6s01jVr1pCRkUFISIizlz48PJyioiK2bt16Tj93hw4dnI9jY2MBnLGlpaXRs2fP08aZn5/P1q1bGT16tDOO4OBgXnzxxQpx9OrViz//+c+88MILPP744/Tt2/eMMRUWFuLv719h23333ce0adPo1KkTY8aM4bfffnO+lpaWRseOHQkKCnJu69OnDw6Ho8Jw+fbt21dpHveaNWuYPHlyhZ8pNTUVh8PB9u3bSUtLw9vbu0JvfEpKCmFhYaecKyAgoMK/DxERqb9USE1EROo9f39/Lr/8ci6//HLGjRvHXXfdxTPPPMPIkSPZsWMHV199Nffddx8vvfQS4eHhLFy4kNGjR1NSUkJgYCAAPj4+Fc5psVgq3eZwOKod57Fjx+jatWul84gjIyPP6VwnxlY+p7mqsZXPmf73v/99SnLu5eXlfOxwOFi0aBFeXl5kZGSc9bwREREcPXq0wrbBgwezc+dOpk+fzsyZMxkwYAAPPPAAr776apViBSok5Wdy7Ngx7r33Xh566KFTXktISGDz5s1VvuaRI0fOuU1EROTCpJ5uERGRk7Rp08ZZPGvFihU4HA5ee+01LrroIlq2bMnevXtr7Fq///77Kc9bt25d6b5dunRhy5YtREVF0bx58wq3Bg0a1FhMrVu3ZsmSJaeNMzo6mri4OLZt23ZKHElJSc79/v73v7Np0ybmz5/Pzz//XKEAWmU6d+7Mxo0bT9keGRnJiBEj+PTTT3njjTd4//33nXGuWbOmQqGzRYsWYbVaadWq1Rmv5evrW6GoHZi/340bN57yMzVv3hxfX19SUlIoLS1lxYoVzmPS09PJzs4+5fzr16+nc+fOZ4xBRETqByXdIiJSbx0+fJjLLruMTz/9lLVr17J9+3a++OILXnnlFYYOHQpA8+bNsdlsvPXWW2zbto1PPvmEd999t8ZiWLRoEa+88gqbN2/m7bff5osvvuDhhx+udN/hw4cTERHB0KFDWbBgAdu3b2fevHk89NBD7Nmzp8Zievjhh/noo4+YNGkSmzdv5plnnmHDhg0V9nnuuecYP348EydOZPPmzaxbt45Jkybx+uuvA7Bq1SqefvppPvjgA/r06cPrr7/Oww8/zLZt20573dTUVDZs2FCht/vpp5/m22+/JSMjgw0bNvDDDz84v5QYPnw4/v7+jBgxgvXr1zN37lz+9Kc/cfvttxMdHX3GnzExMZFff/2VzMxMDh06BMATTzzBb7/9xoMPPsjq1avZsmUL3377rbOQWqtWrRg0aBD33nsvS5YsYcWKFdx1110EBASccv4FCxZwxRVXVOG3LSIiFzol3SIiUm8FBwfTs2dP/vGPf3DJJZfQrl07xo0bx9133+2sst2xY0def/11JkyYQLt27fjss88YP358jcXw+OOPs3z5cjp37syLL77I66+/TmpqaqX7BgYG8uuvv5KQkMD1119P69atnct5hYaG1lhMN910E+PGjWPMmDF07dqVnTt3ct9991XY56677uKDDz5g0qRJtG/fnn79+jF58mSSkpIoKiritttuY+TIkQwZMgSAe+65h0svvZTbb7/9lB7mcu3bt6dLly58/vnnzm2+vr48+eSTdOjQgUsuuQQvLy/nUlyBgYHMmDGDI0eO0L17d2644QYGDBhwxgrp5Z5//nl27NhBcnKycxh4hw4dmD9/Pps3b+biiy+mc+fOPP3008TFxTmPmzRpEnFxcfTr14/rr7+ee+65h6ioqArnXrx4MTk5Odxwww1V+G2LiMiFzmIYhuHuIEREROqjxMREHnnkER555BF3h+IxfvzxR/7yl7+wfv16rNa62Tdw00030bFjR5566il3hyIiIh5AhdRERETEY1x11VVs2bKFzMxM4uPj3R3OOSspKaF9+/Y8+uij7g5FREQ8hJJuERER8Sh1ueff19eXv/71r+4OQ0REPIiGl4uIiIiIiIi4SN2cLCUiIiIiIiJSByjpFhEREREREXERJd0iIiIiIiIiLqKkW0RERERERMRFlHSLiIiIiIiIuIiSbhEREREREREXUdItIiIiIiIi4iJKukVERERERERcREm3iIiIiIiIiIv8P18Fu8GZpET0AAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Test MSE: 0.411219\n", - "R² Score: 0.6324\n", - "Epoch 26 | Loss: 0.154059\n", - "Epoch 27 | Loss: 0.143227\n", - "Epoch 28 | Loss: 0.149588\n", - "Epoch 29 | Loss: 0.142508\n", - "Epoch 30 | Loss: 0.129562\n", - "Epoch 31 | Loss: 0.133575\n", - "Epoch 32 | Loss: 0.134543\n", - "Epoch 33 | Loss: 0.130371\n", - "Epoch 34 | Loss: 0.120946\n", - "Epoch 35 | Loss: 0.122995\n", - "Epoch 36 | Loss: 0.123962\n", - "Epoch 37 | Loss: 0.126102\n", - "Epoch 38 | Loss: 0.127146\n", - "Epoch 39 | Loss: 0.121188\n", - "Epoch 40 | Loss: 0.123526\n", - "Epoch 41 | Loss: 0.116458\n", - "Epoch 42 | Loss: 0.119750\n", - "Epoch 43 | Loss: 0.122837\n", - "Epoch 44 | Loss: 0.119757\n", - "Epoch 45 | Loss: 0.113774\n", - "Epoch 46 | Loss: 0.114809\n", - "Epoch 47 | Loss: 0.109918\n", - "Epoch 48 | Loss: 0.105860\n", - "Epoch 49 | Loss: 0.106406\n", - "Epoch 50 | Loss: 0.105855\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAHqCAYAAAAZLi26AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd3hb1fnA8a+W5T3jle0MsncYSYAQCAmEFcoeDVCgtGwopUBLgcKPlBZoKFACpew9A4UAGRACJUD23suZHvHesnR/fxxdybJlW9OS4/fzPH4kXd17daQryXrve857DJqmaQghhBBCCCGEECLkjJFugBBCCCGEEEIIcbSSoFsIIYQQQgghhAgTCbqFEEIIIYQQQogwkaBbCCGEEEIIIYQIEwm6hRBCCCGEEEKIMJGgWwghhBBCCCGECBMJuoUQQgghhBBCiDCRoFsIIYQQQgghhAgTCbqFEEIIIYQQQogwkaBbCCFERPXt25err77adXvJkiUYDAaWLFkSsscwGAw8+OCDIduf8M++ffuIjY3lf//7X6SbEjK+vk8ffPBBDAYDxcXFHdOwKDB37lx69+5NfX19pJsihBBRQYJuIYTowl555RUMBoPrLzY2lmOOOYabb76ZgoKCSDfPL/Pnz+8ygbUe8PnyFw3+8pe/cPzxxzNp0qQOf+xHH32UefPmdfjjdpTmn+HW/vr27dthbbr66qtpaGjg+eef77DHFEKIaGaOdAOEEEJE3l/+8hfy8vKoq6vj+++/57nnnmP+/Pls2LCB+Pj4Dm3LySefTG1tLTExMX5tN3/+fJ599lmvgXdtbS1m89HzL2/IkCG8/vrrHsvuvfdeEhMT+eMf/xihVnlXVFTEq6++yquvvhqRx3/00Ue58MILmTlzZkQeP9xOPvnkFu+F6667juOOO45f//rXrmWJiYkd1qbY2FiuuuoqnnzySW655ZaoOfkjhBCRcvT8AhFCCBGwM888k/HjxwPqB3tGRgZPPvkkn3zyCZdddpnXbaqrq0lISAh5W4xGI7GxsSHdZ6j3F2nZ2dlceeWVHsv++te/0q1btxbLm3I4HDQ0NHTo6/HGG29gNps555xzOuwxNU2jrq6OuLi4DnvMSOnXrx/9+vXzWPab3/yGfv36tfleaGxsxOFw+H1yy1cXX3wxf/vb3/jmm2849dRTw/IYQgjRWUj3ciGEEC3oP5J3794NqO6iiYmJ7Ny5kxkzZpCUlMQVV1wBqEBuzpw5DBs2jNjYWLKzs7nhhhsoLS312KemaTzyyCP07NmT+Ph4pkyZwsaNG1s8dmtjZX/66SdmzJhBWloaCQkJjBw5kqeeesrVvmeffRbAa9dqb2O6V69ezZlnnklycjKJiYmcdtpp/Pjjjx7r6F13//e//3HnnXeSmZlJQkIC559/PkVFRW2+ho8//jgGg4G9e/e2uO/ee+8lJibG9Rpt376dCy64gJycHGJjY+nZsyeXXnop5eXlbT5GewwGAzfffDNvvvkmw4YNw2q18uWXX7b6Gu/ZsweDwcArr7zisXzLli1ceOGFpKenExsby/jx4/n00099asO8efM4/vjjW2RafXnOjY2NPPzww/Tv3x+r1Urfvn257777WowV7tu3L2effTZfffUV48ePJy4ujueffx6DwUB1dTWvvvqq6z3RtH7AgQMH+NWvfkV2djZWq5Vhw4bx0ksvtXgO+/fvZ+bMmSQkJJCVlcUdd9zh93jl4uJiLr74YpKTk8nIyOC2226jrq7Odf/kyZMZNWqU120HDRrE9OnT/Xq8pvTj+vjjjzNnzhzX67lp0ybXe3zPnj0e27T1OTzjjDNISUkhPj6eyZMnex2rP27cONLT0/nkk08CbrcQQhwtJNMthBCihZ07dwKQkZHhWtbY2Mj06dM58cQTefzxx13dzm+44QZeeeUVrrnmGm699VZ2797NM888w+rVq/nf//6HxWIB4M9//jOPPPIIM2bMYMaMGaxatYpp06bR0NDQbnsWLlzI2WefTW5uLrfddhs5OTls3ryZzz77jNtuu40bbriBgwcPsnDhwhZdbb3ZuHEjJ510EsnJydx9991YLBaef/55TjnlFL799luOP/54j/VvueUW0tLSeOCBB9izZw9z5szh5ptv5t133231MS6++GLuvvtu3nvvPX7/+9973Pfee+8xbdo00tLSaGhoYPr06dTX13PLLbeQk5PDgQMH+OyzzygrKyMlJaXd59OWr7/+mvfee4+bb76Zbt260bdvX8rKynzefuPGjUyaNIkePXpwzz33kJCQwHvvvcfMmTP58MMPOf/881vd1mazsXz5cn772996LPf1OV933XW8+uqrXHjhhfzud7/jp59+Yvbs2WzevJmPP/7YY59bt27lsssu44YbbuD6669n0KBBvP766y26Wvfv3x+AgoICTjjhBNeJiczMTL744guuvfZaKioquP322wE1NOG0004jPz+fW2+9le7du/P666/z9ddf+/wagno/9O3bl9mzZ/Pjjz/yz3/+k9LSUl577TUAfvnLX3L99dezYcMGhg8f7tpu+fLlbNu2jT/96U9+PZ43L7/8MnV1dfz617/GarWSnp7u1/Zff/01Z555JuPGjeOBBx7AaDTy8ssvc+qpp/Ldd99x3HHHeaw/duzYo6p4nhBCBEwTQgjRZb388ssaoC1atEgrKirS9u3bp73zzjtaRkaGFhcXp+3fv1/TNE276qqrNEC75557PLb/7rvvNEB78803PZZ/+eWXHssLCwu1mJgY7ayzztIcDodrvfvuu08DtKuuusq17JtvvtEA7ZtvvtE0TdMaGxu1vLw8rU+fPlppaanH4zTd10033aS19m8N0B544AHX7ZkzZ2oxMTHazp07XcsOHjyoJSUlaSeffHKL12fq1Kkej3XHHXdoJpNJKysr8/p4ugkTJmjjxo3zWPbzzz9rgPbaa69pmqZpq1ev1gDt/fffb3Nf7Rk2bJg2efJkj2WAZjQatY0bN3osb/4a63bv3q0B2ssvv+xadtppp2kjRozQ6urqXMscDoc2ceJEbeDAgW22aceOHRqgPf300x7LfXnOa9as0QDtuuuu81h+1113aYD29ddfu5b16dNHA7Qvv/yyxX4SEhI83l+6a6+9VsvNzdWKi4s9ll966aVaSkqKVlNTo2maps2ZM0cDtPfee8+1TnV1tTZgwACvr2FzDzzwgAZo5557rsfyG2+8UQO0tWvXapqmaWVlZVpsbKz2hz/8wWO9W2+9VUtISNCqqqrafJymmj9n/bgmJydrhYWFHuvq7/Hdu3d7LG/+HnE4HNrAgQO16dOne3wWampqtLy8PO30009v0Y5f//rXWlxcnM/tFkKIo5V0LxdCCMHUqVPJzMykV69eXHrppSQmJvLxxx/To0cPj/WaZyzff/99UlJSOP300ykuLnb9jRs3jsTERL755hsAFi1aRENDQ4uiSno2sS2rV69m9+7d3H777aSmpnrcF0iBJrvdzoIFC5g5c6bHWNjc3Fwuv/xyvv/+eyoqKjy2+fWvf+3xWCeddBJ2u91r1/GmLrnkElauXOnqOQDw7rvvYrVaOe+88wBc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cuVKcnNzef31111T5vlq9+7dnHvuuZxxxhksW7aMN954g8svv7zF3Nxjxoxh+PDhvP/++wwZMoSxY8f69Tj+GjZsGCeccAL33nsvJSUlpKen884777Q4KWI0GnnxxRc588wzGTZsGNdccw09evTgwIEDfPPNNyQnJ/Pf//7Xtf7KlSspKSlxFQwUQoguLUJV04UQQkQBfbqg5cuXt7neVVddpSUkJLR6/wsvvKCNGzdOi4uL05KSkrQRI0Zod999t3bw4EHXOna7XXvooYe03NxcLS4uTjvllFO0DRs2aH369GlzyjDd999/r51++ulaUlKSlpCQoI0cOdJjKqrGxkbtlltu0TIzMzWDweAxfRjNpgzTNE1btWqVNn36dC0xMVGLj4/XpkyZov3www8+vT6ttbE1//73vzVAS0pK0mpraz3u27Vrl/arX/1K69+/vxYbG6ulp6drU6ZM0RYtWuTTvnWtTRl20003eV2/qKhIu+CCC7T4+HgtLS1Nu+GGG7QNGza0mDJM0zRt586d2qxZs7ScnBzNYrFoPXr00M4++2ztgw8+aLddBQUFmtls1l5//XW/n7PNZtMeeughLS8vT7NYLFqvXr20e++912P6Mk1TU4adddZZXh9/y5Yt2sknn6zFxcW1mJ6uoKBAu+mmm7RevXppFotFy8nJ0U477TTthRde8NjH3r17tXPPPVeLj4/XunXrpt12222uafF8nTJs06ZN2oUXXqglJSVpaWlp2s0339zivaD729/+pgHao48+2ua+W9PalGF///vfva6/c+dOberUqZrVatWys7O1++67T1u4cKHX57d69WrtF7/4hZaRkaFZrVatT58+2sUXX6wtXrzYY70//OEPWu/evT2mFxNCiK7KoGntVIERQgghhAjCtddey7Zt21xVuUXbnnrqKe644w727NnTonJ+Z1BfX0/fvn255557uO222yLdHCGEiDgJuoUQQggRVvn5+RxzzDEsXryYSZMmRbo5UU3TNEaNGkVGRoZrnvvOZu7cuTz66KNs374dq9Ua6eYIIUTESdAthBBCCBFh1dXVfPrpp3zzzTf8+9//5pNPPuHcc8+NdLOEEEKEgATdQgghhBARtmfPHvLy8khNTeXGG2/k//7v/yLdJCGEECEiQbcQQgghhBBCCBEmMk+3EEIIIYQQQggRJhJ0CyGEEEII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GgFu2gykBLS9pmksXryYr776iltuuYWioiISEhJ48cUXXd3K33jjDRwOBy+++CIGZ3XBl19+mdTUVJYsWcK0adOYM2cO9957L7/4xS8AmDt3Ll999VWrj7tt2zbee+89Fi5cyNSpUwHo16+f6369K3pWVpZrTHd9fT2PPvooixYtYsKECa5tvv/+e55//nkmT57Mc889R//+/XniiScAGDRoEOvXr+exxx4L6PURQgghhBCi09A0+PYxWDJb3R51OZz7TzD5VlepPUu3FfGbN1ZS02BnVK9UXr76WNITfBiKOvFmSM6F7Qth6oMhaUtnIUF3F/bZZ5+RmJiIzWbD4XBw+eWX8+CDD3LTTTcxYsQIj3Hca9euZceOHSQleZ4dq6urY+fOnZSXl3Po0CGOP/54131ms5nx48e36GKuW7NmDSaTicmTJ/vc5h07dlBTU8Ppp5/usbyhoYExY8YAsHnzZo92AK4AXQghhBBCiKOWvRE+v0NluQFOuksVKwvRlFyfrj3I795bg82ucdLAbsy9chwJVj9CyuEXqL8uRoLuULLEw30HO/xhHQ4H1Db6vd2UKVN47rnniImJoXv37h5VyxMSEjzWraqqYty4cbz55pst9pOZmel/o8HVXdwfVVVVAHz++ef06NHD4z6r1RpQO4QQQgghhOj0Gqrh/Wtg+1dqzPSMx+HYa0O2+9eW7eGBTzeiaXD2yFyevHg0MWYpEeYLCbpDyWCAmIT21ws1hwPqKvzeLCEhgQEDfCukMHbsWN59912ysrJITk72uk5ubi4//fQTJ598MgCNjY2sXLmSsWPHel1/xIgROBwOvv32W1f38qb0TLvdbnctGzp0KFarlfz8/FYz5EOGDOHTTz/1WPbjjz+2/ySFEEIIIYQIt8Z6WPEydB8DvY9vf31flO+H92bBgZVgjoULX4LBZ4Vk15qm8Y9F2/nn4u0AzJrQhwfPGYbRGJrseVcgpyaET6644gq6devGeeedx3fffcfu3btZsmQJt956K/v37wfgtttu469//Svz5s1jy5Yt3HjjjZSVlbW6z759+3LVVVfxq1/9innz5rn2+d577wHQp08fDAYDn332GUVFRVRVVZGUlMRdd93FHXfcwauvvsrOnTtZtWoVTz/9tGve8d/85jds376d3//+92zdupW33nqLV155JdwvkRBCCCGEEO3b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trYWH374Ia677jqsWLEi3MsiAAcOHMBdd92FxYsXw2w2h3s5dBw52wMAw4cPx7hx45Cbm4v3338fMTExYVwZyZxOJ8aMGYM///nPAICRI0di27ZteP3113HdddeFeXXk7a233sL555+P7OzscC+FXN5//328++67WLRoEYYMGYJNmzbh7rvvRnZ2dkS8flheHsHS0tJgMBhO6Jx49OhRZGVlhWlV1B75ePBYhd8dd9yBzz//HMuWLUOfPn3cl2dlZcFms6GmpqbN7XmMQstkMqF///4YPXo05s2bhxEjRuCll17i8dGADRs2oLy8HKNGjUJUVBSioqKwYsUKvPzyy4iKikJmZiaPkYYkJSVh4MCBKCkp4etHI3r16oWTTjqpzWWDBw92bwPgewVt2L9/P5YsWYKbbrrJfRlfQ+F33333Yc6cObjqqqswbNgwXHvttZg9ezbmzZsHQPuvHwbdEcxkMmH06NFYunSp+zKn04mlS5di/PjxYVwZHS8/Px9ZWVltjlVdXR3WrFnDYxUikiThjjvuwP/+9z989913yM/Pb3P96NGjYTQa2xyj4uJilJaW8hiFkdPpREtLC4+PBkyaNAlbt27Fpk2b3P/GjBmDmTNnus/zGGlHQ0MDdu/ejV69evH1oxETJ048YVTlzp07kZubC4DvFbRi/vz5yMjIwAUXXOC+jK+h8LNardDr24auBoMBTqcTQAS8fsLdyY2C895770nR0dHSggULpF9++UW65ZZbpKSkJKmsrCzcS+tx6uvrpY0bN0obN26UAEgvvPCCtHHjRmn//v2SJEnSU089JSUlJUmffPKJtGXLFmn69OlSfn6+1NTUFOaV9wy33XablJiYKC1fvlw6cuSI+5/VanXf5re//a3Ut29f6bvvvpPWr18vjR8/Xho/fnwYV92zzJkzR1qxYoW0d+9eacuWLdKcOXMknU4nffvtt5Ik8fhokXf3ckniMQqne++9V1q+fLm0d+9eafXq1dLkyZOltLQ0qby8XJIkHhstWLt2rRQVFSU9+eST0q5du6R3331Xio2Nld555x33bfheIbwcDofUt29f6YEHHjjhOr6Gwuu6666TevfuLX3++efS3r17pY8++khKS0uT7r//fvdttPz6YdDdDbzyyitS3759JZPJJI0dO1b66aefwr2kHmnZsmUSgBP+XXfddZIkiVEGc+fOlTIzM6Xo6Ghp0qRJUnFxcXgX3YO0d2wASPPnz3ffpqmpSfrd734nJScnS7GxsdIll1wiHTlyJHyL7mFuuOEGKTc3VzKZTFJ6ero0adIkd8AtSTw+WnR80M1jFD5XXnml1KtXL8lkMkm9e/eWrrzySqmkpMR9PY+NNnz22WfS0KFDpejoaKmwsFD6+9//3uZ6vlcIr2+++UYC0O7PnK+h8Kqrq5PuuusuqW/fvpLZbJYKCgqkP/7xj1JLS4v7Nlp+/egkSZLCkmInIiIiIiIi6ua4p5uIiIiIiIhIJQy6iYiIiIiIiFTCoJuIiIiIiIhIJQy6iYiIiIiIiFTCoJuIiIiIiIhIJQy6iYiIiIiIiFTCoJuIiIiIiIhIJQy6iYiIiIiIiFTCoJuIiEjDdDodPv7443AvA4888ghOPvlk1Z+nsrISGRkZ2Ldvn+rPpZS8vDy8+OKLAACbzYa8vDysX78+vIsiIiLNYNBNREQ92rFjx3Dbbbehb9++iI6ORlZWFqZMmYLVq1eHe2mK2LdvH3Q6HTZt2hTupfjkySefxPTp05GXl6fq83gHykoymUz4v//7PzzwwAOKPzYREUWmqHAvgIiIKJxmzJgBm82GhQsXoqCgAEePHsXSpUtRWVkZ7qX1OFarFW+99Ra++eYb1Z7DZrPBZDKp9vgAMHPmTNx7773Yvn07hgwZoupzERGR9jHTTUREPVZNTQ1WrlyJp59+GmeffTZyc3MxduxYPPjgg/jVr37lvt0LL7yAYcOGIS4uDjk5Ofjd736HhoYG9/ULFixAUlISPv/8cwwaNAixsbG47LLLYLVasXDhQuTl5SE5ORl33nknHA6H+355eXl4/PHHcfXVVyMuLg69e/fGq6++2umaDxw4gCuuuAJJSUlISUnB9OnT/SrFXr58OXQ6HZYuXYoxY8YgNjYWEyZMQHFxcZvbPfXUU8jMzER8fDxuvPFGNDc3n/BYb775JgYPHgyz2YzCwkL87W9/c193ww03YPjw4WhpaQEggt2RI0fiN7/5TYdr+/LLLxEdHY1TTz3VfVl1dTVmzpyJ9PR0xMTEYMCAAZg/f777+q1bt+Kcc85BTEwMUlNTccstt7Q5NrNmzcLFF1+MJ598EtnZ2Rg0aBDOOuss7N+/H7NnJgrDrgAACepJREFUz4ZOp4NOp3PfftWqVTj99NMRExODnJwc3HnnnWhsbHRfX15ejosuuggxMTHIz8/Hu+++e8L3kZycjIkTJ+K9997r8HslIqKeg0E3ERH1WBaLBRaLBR9//LE7OGyPXq/Hyy+/jO3bt2PhwoX47rvvcP/997e5jdVqxcsvv4z33nsPX3/9NZYvX45LLrkEX375Jb788ku8/fbbeOONN/Dhhx+2ud+zzz6LESNGYOPGjZgzZw7uuusuLF68uN112O12TJkyBfHx8Vi5ciVWr14Ni8WCqVOnwmaz+fW9//GPf8Tzzz+P9evXIyoqCjfccIP7uvfffx+PPPII/vznP2P9+vXo1atXm4AaAN5991089NBDePLJJ1FUVIQ///nPmDt3LhYuXAgAePnll9HY2Ig5c+a4n6+mpgZ//etfO1zTypUrMXr06DaXzZ07F7/88gu++uorFBUV4bXXXkNaWhoAoLGxEVOmTEFycjLWrVuHDz74AEuWLMEdd9zR5jGWLl2K4uJiLF68GJ9//jk++ugj9OnTB4899hiOHDmCI0eOAAB2796NqVOnYsaMGdiyZQv+85//YNWqVW0eb9asWThw4ACWLVuGDz/8EH/7299QXl5+wvcyduxYrFy5ssvjQEREPYBERETUg3344YdScnKyZDabpQkTJkgPPvigtHnz5k7v88EHH0ipqanur+fPny8BkEpKStyX3XrrrVJsbKxUX1/vvmzKlCnSrbfe6v46NzdXmjp1apvHvvLKK6Xzzz/f/TUA6X//+58kSZL09ttvS4MGDZKcTqf7+paWFikmJkb65ptv2l3r3r17JQDSxo0bJUmSpGXLlkkApCVLlrhv88UXX0gApKamJkmSJGn8+PHS7373uzaPM27cOGnEiBHur/v16yctWrSozW0ef/xxafz48e6vf/jhB8loNEpz586VoqKipJUrV7a7Rtn06dOlG264oc1lF110kXT99de3e/u///3vUnJystTQ0NDme9Hr9VJZWZkkSZJ03XXXSZmZmVJLS0ub++bm5kp/+ctf2lx24403Srfcckuby1auXCnp9XqpqalJKi4ulgBIa9eudV9fVFQkATjhsV566SUpLy+v0++XiIh6Bma6iYioR5sxYwYOHz6MTz/9FFOnTsXy5csxatQoLFiwwH2bJUuWYNKkSejduzfi4+Nx7bXXorKyElar1X2b2NhY9OvXz/11ZmYm8vLyYLFY2lx2fFZ0/PjxJ3xdVFTU7lo3b96MkpISxMfHu7P0KSkpaG5uxu7du/36vocPH+4+36tXLwBwr62oqAjjxo3rcJ2NjY3YvXs3brzxRvc6LBYLnnjiiTbrGD9+PP7v//4Pjz/+OO69916cdtppna6pqakJZrO5zWW33XYb3nvvPZx88sm4//778cMPP7ivKyoqwogRIxAXF+e+bOLEiXA6nW3K5YcNG+bTPu7NmzdjwYIFbb6nKVOmwOl0Yu/evSgqKkJUVFSbbHxhYSGSkpJOeKyYmJg2vx9ERNRzsZEaERH1eGazGeeeey7OPfdczJ07FzfddBMefvhhzJo1C/v27cOFF16I2267DU8++SRSUlKwatUq3HjjjbDZbIiNjQUAGI3GNo+p0+navczpdAa8zoaGBowePbrdfcTp6el+PZb32uQ9zb6uTd4z/Y9//OOE4NxgMLjPO51OrF69GgaDASUlJV0+blpaGqqrq9tcdv7552P//v348ssvsXjxYkyaNAm33347nnvuOZ/WCqBNUN6ZhoYG3HrrrbjzzjtPuK5v377YuXOnz89ZVVXl9zEhIqLuiZluIiKi45x00knu5lkbNmyA0+nE888/j1NPPRUDBw7E4cOHFXuun3766YSvBw8e3O5tR40ahV27diEjIwP9+/dv8y8xMVGxNQ0ePBhr1qzpcJ2ZmZnIzs7Gnj17TlhHfn6++3bPPvssduzYgRUrVuDrr79u0wCtPSNHjsQvv/xywuXp6em47rrr8M477+DFF1/E3//+d/c6N2/e3KbR2erVq6HX6zFo0KBOn8tkMrVpageIn+8vv/xywvfUv39/mEwmFBYWorW1FRs2bHDfp7i4GDU1NSc8/rZt2zBy5MhO10BERD0Dg24iIuqxKisrcc455+Cdd97Bli1bsHfvXnzwwQd45plnMH36dABA//79Ybfb8corr2DPnj14++238frrryu2htWrV+OZZ57Bzp078eqrr+KDDz7AXXfd1e5tZ86cibS0NEyfPh0rV67E3r17sXz5ctx55504ePCgYmu666678M9//hPz58/Hzp078fDDD2P79u1tbvPoo49i3rx5ePnll7Fz505s3boV8+fPxwsvvAAA2LhxIx566CG8+eabmDhxIl544QXcdddd2LNnT4fPO2XKFGzfvr1Ntvuhhx7CJ598gpKSEmzfvh2ff/65+0OJmTNnwmw247rrrsO2bduwbNky/P73v8e1116LzMzMTr/HvLw8fP/99zh06BAqKioAAA888AB++OEH3HHHHdi0aRN27dqFTz75xN1IbdCgQZg6dSpuvfVWrFmzBhs2bMBNN92EmJiYEx5/5cqVOO+883z4aRMRUXfHoJuIiHosi8WCcePG4S9/+QvOOOMMDB06FHPnzsXNN9/s7rI9YsQIvPDCC3j66acxdOhQvPvuu5g3b55ia7j33nuxfv16jBw5Ek888QReeOEFTJkypd3bxsbG4vvvv0ffvn1x6aWXYvDgwe5xXgkJCYqt6corr8TcuXNx//33Y/To0di/fz9uu+22Nre56aab8Oabb2L+/PkYNmwYzjzzTCxYsAD5+flobm7Gr3/9a8yaNQsXXXQRAOCWW27B2WefjWuvvfaEDLNs2LBhGDVqFN5//333ZSaTCQ8++CCGDx+OM844AwaDwT2KKzY2Ft988w2qqqpwyimn4LLLLsOkSZM67ZAue+yxx7Bv3z7069fPXQY+fPhwrFixAjt37sTpp5+OkSNH4qGHHkJ2drb7fvPnz0d2djbOPPNMXHrppbjllluQkZHR5rF//PFH1NbW4rLLLvPhp01ERN2dTpIkKdyLICIi6ony8vJw99134+677w73UjTjiy++wH333Ydt27ZBr4/M3MCVV16JESNG4A9/+EO4l0JERBrARmpERESkGRdccAF27dqFQ4cOIScnJ9zL8ZvNZsOwYcMwe/bscC+FiIg0gkE3ERERaUokZ/5NJhP+9Kc/hXsZRESkISwvJyIiIiIiIlJJZG6WIiIiIiIiIooADLqJiIiIiIiIVMKgm4iIiIiIiEglDLqJiIiIiIiIVMKgm4iIiIiIiEglDLqJiIiIiIiIVMKgm4iIiIiIiEglDLqJiIiIiIiIVMKgm4iIiIiIiEgl/w/HqNPC2kTZwgAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Test MSE: 0.411573\n", - "R² Score: 0.6321\n", - "Epoch 51 | Loss: 0.106542\n", - "Epoch 52 | Loss: 0.103152\n", - "Epoch 53 | Loss: 0.103517\n", - "Epoch 54 | Loss: 0.092629\n", - "Epoch 55 | Loss: 0.124167\n", - "Epoch 56 | Loss: 0.108883\n", - "Epoch 57 | Loss: 0.107365\n", - "Epoch 58 | Loss: 0.104630\n", - "Epoch 59 | Loss: 0.090972\n", - "Epoch 60 | Loss: 0.092814\n", - "Epoch 61 | Loss: 0.085010\n", - "Epoch 62 | Loss: 0.077792\n", - "Epoch 63 | Loss: 0.078269\n", - "Epoch 64 | Loss: 0.083287\n", - "Epoch 65 | Loss: 0.071687\n", - "Epoch 66 | Loss: 0.082848\n", - "Epoch 67 | Loss: 0.074119\n", - "Epoch 68 | Loss: 0.098934\n", - "Epoch 69 | Loss: 0.062886\n", - "Epoch 70 | Loss: 0.061841\n", - "Epoch 71 | Loss: 0.065269\n", - "Epoch 72 | Loss: 0.060058\n", - "Epoch 73 | Loss: 0.058145\n", - "Epoch 74 | Loss: 0.058967\n", - "Epoch 75 | Loss: 0.075962\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAHqCAYAAAAZLi26AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAD/aElEQVR4nOzdd3hb1fnA8a8ky/Le21nOJHsSSBghQBIIK2WX0oRRoGUFwiihpUBpSWmBhgI/AqWUvSl7mgzChiwgk+ztvbfG/f1xdCXLU5IlS7bfz/P4uRp3HOnKtt57znlfg6ZpGkIIIYQQQgghhAg4Y6gbIIQQQgghhBBC9FYSdAshhBBCCCGEEEEiQbcQQgghhBBCCBEkEnQLIYQQQgghhBBBIkG3EEIIIYQQQggRJBJ0CyGEEEIIIYQQQSJBtxBCCCGEEEIIESQSdAshhBBCCCGEEEEiQbcQQgghhBBCCBEkEnQLIYQIqUGDBnHJJZe47q9atQqDwcCqVasCdgyDwcBdd90VsP0J3+zfv5+oqCi+/PLLUDclYLz9nN51110YDAZKSkq6p2FhYNmyZQwYMIDGxsZQN0UIIcKCBN1CCNGHPf300xgMBtdPVFQUw4cP59prr6WwsDDUzfPJBx980GcCaz3g8+YnHPz5z3/mqKOO4phjjun2Y99777289dZb3X7c7tLyd7i9n0GDBnVbmy655BKampp4/PHHu+2YQggRziJC3QAhhBCh9+c//5m8vDwaGhr44osveOyxx/jggw/YuHEjMTEx3dqW448/nvr6eiIjI33a7oMPPuDRRx9tM/Cur68nIqL3/MsbOXIkzz33nMdjixcvJi4ujj/84Q8halXbiouLeeaZZ3jmmWdCcvx7772Xc889l3nz5oXk+MF2/PHHt/os/OY3v2Hq1KlceeWVrsfi4uK6rU1RUVEsWLCABx98kOuuuy5sLv4IIUSo9J5vIEIIIfx26qmnMmXKFEB9YU9NTeXBBx/k7bff5pe//GWb29TW1hIbGxvwthiNRqKiogK6z0DvL9QyMzO5+OKLPR7729/+RlpaWqvHm3M4HDQ1NXXr+/H8888TERHBGWec0W3H1DSNhoYGoqOju+2YoTJ48GAGDx7s8dhvf/tbBg8e3OFnwWaz4XA4fL645a3zzz+fv//976xcuZITTzwxKMcQQoieQoaXCyGEaEX/krx7925ADReNi4tj586dzJ07l/j4eH71q18BKpBbunQpo0ePJioqiszMTK666irKy8s99qlpGn/5y1/o168fMTExzJw5k02bNrU6dntzZb/99lvmzp1LcnIysbGxjBs3joceesjVvkcffRSgzaHVbc3pXr9+PaeeeioJCQnExcVx0kkn8c0333isow/d/fLLL1m0aBHp6enExsbyi1/8guLi4g7fw/vvvx+DwcDevXtbPbd48WIiIyNd79H27ds555xzyMrKIioqin79+nHhhRdSWVnZ4TE6YzAYuPbaa3nhhRcYPXo0FouFjz76qN33eM+ePRgMBp5++mmPx7du3cq5555LSkoKUVFRTJkyhXfeecerNrz11lscddRRrXpavXnNNpuNe+65hyFDhmCxWBg0aBC33357q7nCgwYN4vTTT+fjjz9mypQpREdH8/jjj2MwGKitreWZZ55xfSaa5w84ePAgl112GZmZmVgsFkaPHs1TTz3V6jUcOHCAefPmERsbS0ZGBjfeeKPP85VLSko4//zzSUhIIDU1lYULF9LQ0OB6fsaMGYwfP77NbUeMGMGcOXN8Ol5z+nm9//77Wbp0qev93Lx5s+szvmfPHo9tOvo9POWUU0hMTCQmJoYZM2a0OVd/8uTJpKSk8Pbbb/vdbiGE6C2kp1sIIUQrO3fuBCA1NdX1mM1mY86cORx77LHcf//9rmHnV111FU8//TSXXnop119/Pbt37+aRRx5h/fr1fPnll5jNZgD+9Kc/8Ze//IW5c+cyd+5c1q1bx+zZs2lqauq0Pfn5+Zx++ulkZ2ezcOFCsrKy2LJlC++99x4LFy7kqquu4tChQ+Tn57caatuWTZs2cdxxx5GQkMCtt96K2Wzm8ccf54QTTuCzzz7jqKOO8lj/uuuuIzk5mTvvvJM9e/awdOlSrr32Wl555ZV2j3H++edz66238uqrr3LLLbd4PPfqq68ye/ZskpOTaWpqYs6cOTQ2NnLdddeRlZXFwYMHee+996ioqCAxMbHT19ORFStW8Oqrr3LttdeSlpbGoEGDqKio8Hr7TZs2ccwxx5Cbm8ttt91GbGwsr776KvPmzeONN97gF7/4RbvbWq1Wvv/+e373u995PO7ta/7Nb37DM888w7nnnstNN93Et99+y5IlS9iyZQtvvvmmxz63bdvGL3/5S6666iquuOIK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ezerVq3i+uuv58CBAwAsXLiQv/3tb7z11lts3bqVq6++moqKinb3OWjQIBYsWMBll13GW2+95drnq6++CsDAgQMxGAy89957FBcXU1NTQ3x8PDfffDM33ngjzzzzDDt37mTdunU8/PDDrrrjv/3tb9m+fTu33HIL27Zt48UXX+Tpp58O9lskeju9lxukp1sIIYQQ/qsvU8uOgu4woWkaj67ayRZrJgCXDG/i0V9NCmzADVB9SOXHMkZA8qDA7jsMSNAtvBITE8Pq1asZMGAAZ599NiNHjuTyyy+noaHB1fN900038etf/5oFCxYwbdo04uPj+cUvftHhfh977DHOPfdcrr76ao444giuuOIKamtVQJObm8vdd9/NbbfdRmZmJtdeey0A99xzD3fccQdLlixh5MiRnHLKKbz//vvk5eUBMGDAAN544w3eeustxo8fz7Jly7j33nuD+O6IPqF50B2KKgVCCCGE6B286ekOA002B39+bzP/+HgbuzRVn/vioVZMxiAMcdfncyfngSmAQ9bDhAwv76M66vlt77msrCxXb3JbIiIiWLp0KUuXLm13nVWrVnncj4qK4sEHH+TBBx9sc/077riDO+64w+Mxg8HAwoULWbhwYbvHOf300zn99NM9Hrv00kvbXV+ITklPtxBCCCECwRV0p4S2HU41jTZ2FtWwo6iGHcVqubOohr1lddgdKonbgGHjYddXGPQh4IHmylze++ZzgwTdQgjhnerD7tsyp1sIIYQQ/gphT7fdoZG/uYBvdpWx0xlgH65saHf95Bgzd505mkmmetiFe951oLlqdHuXb6qnkaBbCCG8IT3dQgghhAiEEATdVruDdzYc4tFVO9hV3Pp7TFqchaEZsQzNiGNoehxDM+IZmhFHZoIFg8EAh53JzfTgONB6ceZykKBbCCG8U9M86JY53UIIIYTwg6Z1a9DdaLPzxtqDPPbZDvaX1QOQGG3mFxNzOSJLBdZDM+JIionseEepzh7oulKoK4OYAA+NL3H2oPfCGt0gQbcQQnjHo6dbhpcLIYQQwg9NNaruNgQ16G6w2nn5u308vnqXa/h4amwkvzluMBcfPcD3+tqRsZCQC1UH1RDzmKmBa2xTLVSpaki9sVwYSNAthBDe8ZjTLcPLhRBCCOEHvZfbZAFzdMB3X9to4/lv9vLvz3dTUtMIQGaChauOH8Iv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uffee/n6668ZN24cM2fO5ODBg8EeWr9TUVHBuHHjePrpp1u8/bHHHuPJJ5/k2WefZfny5URHRzNz5kyqq6t7eKT90+LFi7nhhhv44osvWLBgAW63m3POOYeKigrfMbfeeiv//e9/ef3111m8eDH79u3j4osvDuKo+5fBgwfz61//mpUrV7JixQrOPvtsZs+ezYYNGwDNTyj56quv+POf/8zYsWObXK85Cq7jjjuO/fv3+/599tlnvts0N8F35MgRTj31VFwuF++99x4bN27kd7/7HYmJib5j9FkheL766qsmr58FCxYAcOmllwJ6DQXbo48+yjPPPMMf//hHNm3axKOPPspjjz3GU0895TsmpF8/hvRqkyZNMm644QbfZY/HY2RkZBiPPPJIEEclgPHWW2/5Lnu9XiMtLc34zW9+47uuuLjYCA8PN/7xj38EYYRy8OBBAzAWL15sGIY5Hy6Xy3j99dd9x2zatMkAjGXLlgVrmP1eYmKi8dxzz2l+QkhZWZkxYsQIY8GCBcaZZ55p3HzzzYZh6DUUbPfee68xbty4Fm/T3ISGO+64wzjttNNavV2fFULLzTffbAwbNszwer16DYWA888/37j66qubXHfxxRcbV1xxhWEYof/6Uaa7F6utrWXlypVMnz7dd53dbmf69OksW7YsiCOTo+3YsYOCgoImcxUfH8/kyZM1V0FSUlICQFJSEgArV67E7XY3maNRo0aRlZWlOQoCj8fDq6++SkVFBVOmTNH8hJAbbriB888/v8lcgF5DoWDr1q1kZGQwdOhQrrjiCnbv3g1obkLFf/7zH0488UQuvfRSBg4cyIQJE/jrX//qu12fFUJHbW0tL730EldffTU2m02voRBwyimnsHDhQrZs2QLAmjVr+Oyzzzj33HOB0H/9OIM9AOm8wsJCPB4PqampTa5PTU1l8+bNQRqVtKSgoACgxbmybpOe4/V6ueWWWzj11FM5/vjjAXOOwsLCSEhIaHKs5qhnrVu3jilTplBdXU1MTAxvvfUWxx57LKtXr9b8hIBXX32Vr7/+mq+++qrZbXoNBdfkyZOZP38+I0eOZP/+/dx///2cfvrprF+/XnMTIrZv384zzzzDT3/6U37xi1/w1VdfcdNNNxEWFsZVV12lzwoh5O2336a4uJi5c+cCen8LBXfeeSelpaWMGjUKh8OBx+Ph4Ycf5oorrgBC/7O2gm4R6XduuOEG1q9f32S/o4SGkSNHsnr1akpKSnjjjTe46qqrWLx4cbCHJcCePXu4+eabWbBgAREREcEejhzFyvYAjB07lsmTJzNkyBBee+01IiMjgzgysXi9Xk488UR+9atfATBhwgTWr1/Ps88+y1VXXRXk0Uljzz//POeeey4ZGRnBHorUe+2113j55Zd55ZVXOO6441i9ejW33HILGRkZveL1o+XlvVhycjIOh6NZ5cQDBw6QlpYWpFFJS6z50FwF34033sg777zDokWLGDx4sO/6tLQ0amtrKS4ubnK85qhnhYWFMXz4cCZOnMgjjzzCuHHj+MMf/qD5CQErV67k4MGDnHDCCTidTpxOJ4sXL+bJJ5/E6XSSmpqqOQohCQkJHHPMMWzbtk2vnxCRnp7Oscce2+S60aNH+7YB6LNCaNi1axcfffQR1157re86vYaC7+c//zl33nknl19+OWPGjOHKK6/k1ltv5ZFHHgFC//WjoLsXCwsLY+LEiSxcuNB3ndfrZeHChUyZMiWII5Oj5eTkkJaW1mSuSktLWb58ueaqhxiGwY033shbb73Fxx9/TE5OTpPbJ06ciMvlajJHubm57N69W3MURF6vl5qaGs1PCJg2bRrr1q1j9erVvn8nnngiV1xxhe+85ih0lJeXk5eXR3p6ul4/IeLUU09t1qpyy5YtDBkyBNBnhVAxb948Bg4cyPnnn++7Tq+h4KusrMRubxq6OhwOvF4v0AteP8Gu5CZd8+qrrxrh4eHG/PnzjY0bNxrXXXedkZCQYBQUFAR7aP1OWVmZsWrVKmPVqlUGYDz++OPGqlWrjF27dhmGYRi//vWvjYSEBOPf//63sXbtWmP27NlGTk6OUVVVFeSR9w/XX3+9ER8fb3zyySfG/v37ff8qKyt9x/zoRz8ysrKyjI8//thYsWKFMWXKFGPKlClBHHX/cueddxqLFy82duzYYaxdu9a48847DZvNZnz44YeGYWh+QlHj6uWGoTkKpttuu8345JNPjB07dhhLly41pk+fbiQnJxsHDx40DENzEwq+/PJLw+l0Gg8//LCxdetW4+WXXzaioqKMl156yXeMPisEl8fjMbKysow77rij2W16DQXXVVddZQwaNMh45513jB07dhhvvvmmkZycbNx+++2+Y0L59aOguw946qmnjKysLCMsLMyYNGmS8cUXXwR7SP3SokWLDKDZv6uuusowDLOVwd13322kpqYa4eHhxrRp04zc3NzgDrofaWluAGPevHm+Y6qqqowf//jHRmJiohEVFWVcdNFFxv79+4M36H7m6quvNoYMGWKEhYUZKSkpxrRp03wBt2FofkLR0UG35ih4LrvsMiM9Pd0ICwszBg0aZFx22WXGtm3bfLdrbkLDf//7X+P44483wsPDjVGjRhl/+ctfmtyuzwrB9cEHHxhAi79zvYaCq7S01Lj55puNrKwsIyIiwhg6dKjxy1/+0qipqfEdE8qvH5thGEZQUuwiIiIiIiIifZz2dIuIiIiIiIgEiIJuERERERERkQBR0C0iIiIiIiISIAq6RURERERERAJEQbeIiIiIiIhIgCjoFhEREREREQkQBd0iIiIiIiIiAaKgW0RERERERCRAFHSLiIiEMJvNxttvvx3sYXDfffcxfvz4gD9PUVERAwcOZOfOnQF/ru6SnZ3NE088AUBtbS3Z2dmsWLEiuIMSEZGQoaBbRET6tUOHDnH99deTlZVFeHg4aWlpzJw5k6VLlwZ7aN1i586d2Gw2Vq9eHeyh+OXhhx9m9uzZZGdnB/R5GgfK3SksLIyf/exn3HHHHd3+2CIi0js5gz0AERGRYJozZw61tbW88MILDB06lAMHDrBw4UKKioqCPbR+p7Kykueff54PPvggYM9RW1tLWFhYwB4f4IorruC2225jw4YNHHfccQF9LhERCX3KdIuISL9VXFzMkiVLePTRRznrrLMYMmQIkyZN4q677uKb3/ym77jHH3+cMWPGEB0dTWZmJj/+8Y8pLy/33T5//nwSEhJ45513GDlyJFFRUVxyySVUVlbywgsvkJ2dTWJiIjfddBMej8d3v+zsbB588EG+/e1vEx0dzaBBg3j66afbHPOePXv41re+RUJCAklJScyePbtDS7E/+eQTbDYbCxcu5MQTTyQqKopTTjmF3NzcJsf9+te/JjU1ldjYWK655hqqq6ubPdZzzz3H6NGjiYiIYNSoUfzpT3/y3Xb11VczduxYampqADPYnTBhAt/73vdaHdu7775LeHg4J598su+6I0eOcMUVV5CSkkJkZCQjRoxg3rx5vtvXrVvH2WefTWRkJAMGDOC6665rMjdz587lwgsv5OGHHyYjI4ORI0cydepUdu3axa233orNZsNms/mO/+yzzzj99NOJjIwkMzOTm266iYqKCt/tBw8e5IILLiAyMpKcnBxefvnlZj9HYmIip556Kq+++mqrP6uIiPQfCrpFRKTfiomJISYmhrffftsXHLbEbrfz5JNPsmHDBl544QU+/vhjbr/99ibHVFZW8uSTT/Lqq6/y/vvv88knn3DRRRfx7rvv8u677/Liiy/y5z//mTfeeKPJ/X7zm98wbtw4Vq1axZ133snNN9/MggULWhyH2+1m5syZxMbGsmTJEpYuXUpMTAyzZs2itra2Qz/7L3/5S373u9+xYsUKnE4nV199te+21157jfvuu49f/epXrFixgvT09CYBNcDLL7/MPffcw8MPP8ymTZv41a9+xd13380LL7wAwJNPPklFRQV33nmn7/mKi4v54x//2OqYlixZwsSJE5tcd/fdd7Nx40bee+89Nm3axDPPPENycjIAFRUVzJw5k8TERL766itef/11PvroI2688cYmj7Fw4UJyc3NZsGAB77zzDm+++SaDBw/mgQceYP/+/ezfvx+AvLw8Zs2axZw5c1i7di3//Oc/+eyzz5o83ty5c9mzZw+LFi3ijTfe4E9/+hMHDx5s9rNMmjSJJUuWtDsPIiLSDxgiIiL92BtvvGEkJiYaERERximnnGLcddddxpo1a9q8z+uvv24MGDDAd3nevHkGYGzbts133Q9/+EMjKirKKCsr8103c+ZM44c//KHv8pAhQ4xZs2Y1eezLLrvMOPfcc32XAeOtt94yDMMwXnzxRWPkyJGG1+v13V5TU2NERkYaH3zwQYtj3bFjhwEYq1atMgzDMBYtWmQAxkcffeQ75n//+58BGFVVVYZhGMaUKVOMH//4x00eZ/Lkyca4ceN8l4cNG2a88sorTY558MEHjSlTpvguf/7554bL5TLuvvtuw+l0GkuWLGlxjJbZs2cbV199dZPrLrjgAuP73/9+i8f/5S9/MRITE43y8vImP4vdbjcKCgoMwzCMq666ykhNTTVqamqa3HfIkCHG73//+ybXXXPNNcZ1113X5LolS5YYdrvdqKqqMnJzcw3A+PLLL323b9q0yQCaPdYf/vAHIzs7u82fV0RE+gdlukVEpF+bM2cO+/bt4z//+Q+zZs3ik08+4YQTTmD+/Pm+Yz766COmTZvGoEGDiI2N5corr6SoqIjKykrfMVFRUQwbNsx3OTU1lezsbGJiYppcd3RWdMqUKc0ub9q0qcWxrlmzhm3bthEbG+vL0iclJVFdXU1eXl6Hfu6xY8f6zqenpwP4xrZp0yYmT57c6jgrKirIy8vjmmuu8Y0jJiaGhx56qMk4pkyZws9+9jMefPBBbrvtNk477bQ2x1RVVUVEREST666//npeffVVxo8fz+23387nn3/uu23Tpk2MGzeO6Oho33WnnnoqXq+3yXL5MWPG+LWPe82aNcyfP7/JzzRz5ky8Xi87duxg06ZNOJ3OJtn4UaNGkZCQ0OyxIiMjm/x9iIhI/6VCaiIi0u9FREQwY8YMZsyYwd133821117Lvffey9y5c9m5cyff+MY3uP7663n44YdJSkris88+45prrqG2tpaoqCgAXC5Xk8e02WwtXuf1ejs9zvLyciZOnNjiPuKUlJQOPVbjsVl7mv0dm7Vn+q9//Wuz4NzhcPjOe71eli5disPhYNu2be0+bnJyMkeOHGly3bnnnsuuXbt49913WbBgAdOmTeOGG27gt7/9rV9jBZoE5W0pLy/nhz/8ITfddFOz27KystiyZYvfz3n48OEOz4mIiPRNynSLiIgc5dhjj/UVz1q5ciVer5ff/e53nHzyyRxzzDHs27ev257riy++aHZ59OjRLR57wgknsHXrVgYOHMjw4cOb/IuPj++2MY0ePZrly5e3Os7U1FQyMjLYvn17s3Hk5OT4jvvNb37D5s2bWbx4Me+//36TAmgtmTBhAhs3bmx2fUpKCldddRUvvfQSTzzxBH/5y19841yzZk2TQmdLly7FbrczcuTINp8rLCysSVE7MH+/GzdubPYzDR8+nLCwMEaNGkVdXR0rV6703Sc3N5fi4uJmj79+/XomTJjQ5hhERKR/UNAtIiL9VlFREWeffTYvvfQSa9euZceOHbz++us89thjzJ49G4Dhw4fjdrt56qmn2L59Oy+++CLPPvtst41h6dKlPPbYY2zZsoWnn36a119/nZtvvrnFY6+44gqSk5OZPXs2S5YsYceOHXzyySfcdNNN7N27t9vGdPPNN/O3v/2NefPmsWXLFu699142bNjQ5Jj777+fRx55hCeffJItW7awbt065s2bx+OPPw7AqlWruOeee3juuec49dRTefzxx7n55pvZvn17q887c+ZMNmzY0CTbfc899/Dvf/+bbdu2sWHDBt555x3flxJXXHEFERERXHXVVaxfv55Fixbxk5/8hCuvvJLU1NQ2f8bs7Gw+/fRT8vPzKSwsBOCOO+7g888/58Ybb2T16tVs3bqVf//7375CaiNHjmTWrFn88Ic/ZPny5axcuZJrr72WyMjIZo+/ZMkSzjnnHD9+2yIi0tcp6BYRkX4rJiaGyZMn8/vf/54zzjiD448/nrvvvpsf/OAHvirb48aN4/HHH+fRRx/l+OOP5+WXX+aRRx7ptjHcdtttrFixggkTJvDQQw/x+OOPM3PmzBaPjYqK4tNPPyUrK4uLL76Y0aNH+9p5xcXFdduYLrvsMu6++25uv/12Jk6cyK5du7j++uubHHPttdfy3HPPMW/ePMaMGcOZZ57J/PnzycnJobq6mu9+97vMnTuXCy64AIDrrruOs846iyuvvLJZhtkyZswYTjjhBF577TXfdWFhYdx1112MHTuWM844A4fD4WvFFRUVxQcffMDhw4c56aSTuOSSS5g2bVqbFdItDzzwADt37mTYsGG+ZeBjx45l8eLFbNmyhdNPP50JEyZwzz33kJGR4bvfvHnzyMjI4Mwzz+Tiiy/muuuuY+DAgU0ee9myZZSUlHDJJZf48dsWEZG+zmYYhhHsQYiIiPRH2dnZ3HLLLdxyyy3BHkrI+N///sfPf/5z1q9fj93eO3MDl112GePGjeMXv/hFsIciIiIhQIXUREREJGScf/75bN26lfz8fDIzM4M9nA6rra1lzJgx3HrrrcEeioiIhAgF3SIiIhJSenPmPywsjP/7v/8L9jBERCSEaHm5iIiIiIiISID0zs1SIiIiIiIiIr2Agm4RERERERGRAFHQLSIiIiIiIhIgCrpFREREREREAkRBt4iIiIiIiEiAKOgWERERERERCRAF3SIiIiIiIiIBoqBbREREREREJEAUdIuIiIiIiIgEyP8DYA8aY5gL6noAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Test MSE: 0.378216\n", - "R² Score: 0.6619\n", - "Epoch 76 | Loss: 0.064937\n", - "Epoch 77 | Loss: 0.050079\n", - "Epoch 78 | Loss: 0.055219\n", - "Epoch 79 | Loss: 0.053107\n", - "Epoch 80 | Loss: 0.065674\n", - "Epoch 81 | Loss: 0.074585\n", - "Epoch 82 | Loss: 0.071789\n", - "Epoch 83 | Loss: 0.063313\n", - "Epoch 84 | Loss: 0.050698\n", - "Epoch 85 | Loss: 0.058837\n", - "Epoch 86 | Loss: 0.049046\n", - "Epoch 87 | Loss: 0.044147\n", - "Epoch 88 | Loss: 0.055033\n", - "Epoch 89 | Loss: 0.046234\n", - "Epoch 90 | Loss: 0.036136\n", - "Epoch 91 | Loss: 0.035275\n", - "Epoch 92 | Loss: 0.047785\n", - "Epoch 93 | Loss: 0.040791\n", - "Epoch 94 | Loss: 0.047713\n", - "Epoch 95 | Loss: 0.042139\n", - "Epoch 96 | Loss: 0.036102\n", - "Epoch 97 | Loss: 0.034951\n", - "Epoch 98 | Loss: 0.038887\n", - "Epoch 99 | Loss: 0.035606\n", - "Epoch 100 | Loss: 0.034245\n" - ] - }, - { - "data": { - "image/png": "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ruDGG2/khhtuYNCgQbz66qsNUq379+8PQEFBAaeeeqrnwkR6ejqfffYZ1113HWVlZdx2222AGppw1llnkZuby+9+9zu6d+/Oq6++ysqVK33+G4J6PfTp04dFixbx3Xff8dRTT1FcXMwrr7wCwFVXXcUNN9zAtm3bGD58uGe/H374gd27d/OnP/3Jr8drzEsvvURNTQ2/+tWviI6OJi0tza/9V65cydlnn83YsWO5//77MRqNvPTSS5x55pl8/fXXjB8/vs72Y8aM6VDF84QQImCaEEKITuull17SAG3FihVaYWGhdujQIe3NN9/UunTposXGxmqHDx/WNE3TFixYoAHa3XffXWf/r7/+WgO0//znP3Vu//zzz+vcfuzYMS0qKko755xzNJfL5dnu3nvv1QBtwYIFnttWrVqlAdqqVas0TdM0h8Oh9e3bV+vdu7dWXFxc53FqH+umm27Smvq3Bmj333+/5/d58+ZpUVFR2r59+zy3HT16VEtMTNTOOOOMBn+f6dOn13ms22+/XTOZTFpJSUmjj6ebOHGiNnbs2Dq3ff/99xqgvfLKK5qmadrmzZs1QHvnnXeaPVZLhg0bpk2ZMqXObYBmNBq17du317m9/t9Yd+DAAQ3QXnrpJc9tZ511ljZixAitpqbGc5vL5dImTZqkDRw4sNk27d27VwO0p59+us7tvjznLVu2aIB2/fXX17n997//vQZoK1eu9NzWu3dvDdA+//zzBseJj4+v8/rSXXfddVq3bt2048eP17n98ssv15KTk7WqqipN0zRt8eLFGqC9/fbbnm0qKyu1AQMGNPo3rO/+++/XAO28886rc/tvf/tbDdC2bt2qaZqmlZSUaDExMdpdd91VZ7vf/e53Wnx8vFZRUdHs49RW/znr5zUpKUk7duxYnW311/iBAwfq3F7/NeJyubSBAwdqs2bNqvNeqKqq0vr27avNmDGjQTt+9atfabGxsT63WwghOipJLxdCCMH06dNJT08nOzubyy+/nISEBN5//3169OhRZ7v6PZbvvPMOycnJzJgxg+PHj3t+xo4dS0JCAqtWrQJgxYoV2Gy2BkWV9N7E5mzevJkDBw5w2223kZKSUue+QAo0OZ1Oli1bxrx58+qMhe3WrRtXXnkl33zzDWVlZXX2+dWvflXnsU4//XScTmejqeO1XXbZZWzcuNGTOQDw1ltvER0dzfnnnw/g6dX94osvqKqq8vv5tGTKlCkMHTo0oH2LiopYuXIll156KeXl5Z7ze+LECWbNmsWePXs4cuRIk/ufOHECgNTU1Dq3+/KcP/30UwAWLlxY5/Y77rgDgE8++aTO7X379vU5BVvTNN577z3OPfdcNE2r89qdNWsWpaWlbNq0ydOObt26cfHFF3v2j4uLq1OkzBc33XRTnd9vueUWz/FB/U3OP/983njjDTRNA9Rr9a233vKktrfWRRddRHp6ekD7btmyhT179nDllVdy4sQJz9+rsrKSs846izVr1uByuersk5qaSnV1dUhe10II0Z5I0C2EEIJnnnmG5cuXs2rVKn7++Wf279/fIIAxm8307Nmzzm179uyhtLSUjIwM0tPT6/xUVFRw7NgxAE9wOnDgwDr7p6enNwjI6tMD1topt61RWFhIVVUVgwYNanDfkCFDcLlcHDp0qM7tvXr1qvO73ub649bru+SSSzAajZ40dE3TeOeddzxjyUEFiwsXLuSFF16ga9euzJo1i2eeeabV47l1ffv2DXjfvXv3omkaf/7znxuc3/vvvx/Ac46boweRtdvU0nPOycnBaDQyYMCAOvtmZWWRkpLS4IKHP8+zsLCQkpISnn/++QbP65prrqnzvHJychgwYECDCzyNvX6aU/+1379/f4xGY52x1PPnzyc3N5evv/4aUBerCgoKuOqqq/x6rKa05rWwZ88eABYsWNDgb/bCCy9gtVobvGb18y7Vy4UQnZ2M6RZCCMH48eM91cubEh0djdFY91qty+UiIyOD//znP43uE2ivWqQxmUyN3l4/mKyve/funH766bz99tvce++9fPfdd+Tm5vK3v/2tznaPP/44V199NR9++CHLli3jd7/7nWfsb/0LHf5qrIJ3U0GQ0+ms87vec/n73/++yV7k+kFxbXpNgMYuTvj6nH0N2PypVK4/r1/+8pcsWLCg0W1Gjhzp8/EC0djzmjVrFpmZmbz22mucccYZvPbaa2RlZTF9+vSgPGYwXguPPvooJ598cqP71C+WV1xcTFxcXKeoIi+EEM2RoFsIIUTA+vfvz4oVK5g8eXKzX6x79+4NqN6y2indhYWFLfYW64Wvtm3b1mzw4Wtwlp6eTlxcHLt27Wpw386dOzEajWRnZ/t0LF9cdtll/Pa3v2XXrl289dZbxMXFNTp91ogRIxgxYgR/+tOfWLt2LZMnT2bJkiX85S9/CVpbdHpPff2CavV7j/VzZbFYAgr8evXqRWxsrKcKfn3NPefevXvjcrnYs2cPQ4YM8exTUFBASUmJ5zXVksZeF+np6SQmJuJ0Olt8Xr1792bbtm1omlbnWI29fpqzZ8+eOj3Ne/fuxeVy0adPH89tJpOJK6+8kqVLl/K3v/2NDz74gBtuuKHJiz7B4OtrQX8fJiUl+fxaOHDgQJ1zJ4QQnZWklwshhAjYpZdeitPp5OGHH25wn8Ph8HyRnz59OhaLhaeffrpO7/DixYtbfIwxY8bQt29fFi9e3CAwqH0sfcxrS5W5TSYTM2fO5MMPP6yT2ltQUMDrr7/Oaaed5kn9DoaLLroIk8nEG2+8wTvvvMPcuXPrjM8tKyvD4XDU2WfEiBEYjUa/p6XyVe/evTGZTKxZs6bO7f/617/q/J6RkcHUqVN57rnnyMvLa3CclqZNs1gsjBs3jg0bNtS53ZfnPGfOHKDha+SJJ54A4Jxzzmn2sXXx8fENXhMmk4mLLrqI9957j23btjXYp/bzmjNnDkePHuXdd9/13FZVVcXzzz/v0+Pr9CntdE8//TQAZ599dp3br7rqKoqLi7nxxhupqKhodq7tYNCD6dqvBafT2eD5jR07lv79+/PYY49RUVHR4DiNvRY2bdrEpEmTgtxiIYRof6SnWwghRMCmTJnCjTfeyKJFi9iyZQszZ87EYrGwZ88e3nnnHZ588kkuvvhi0tPT+f3vf8+iRYuYO3cuc+bMYfPmzXz22Wd07dq12ccwGo08++yznHvuuZx88slcc801dOvWjZ07d7J9+3a++OILQAUFAL/73e+YNWsWJpOJyy+/vNFj/uUvf2H58uWcdtpp/Pa3v8VsNvPcc89htVr5+9//HtS/UUZGBtOmTeOJJ56gvLycyy67rM79K1eu5Oabb+aSSy7hpJNOwuFw8Oqrr3oCw1BITk7mkksu4emnn8ZgMNC/f38+/vjjRsdnP/PMM5x22mmMGDGCG264gX79+lFQUMC6des4fPgwW7dubfaxzj//fP74xz9SVlbmuZjhy3MeNWoUCxYs4Pnnn6ekpIQpU6bw/fff8/LLLzNv3jymTZvm03MdO3YsK1as4IknnqB79+707duXCRMm8Ne//pVVq1YxYcIEbrjhBoYOHUpRURGbNm1ixYoVFBUVAXDDDTfwz3/+k/nz57Nx40a6devGq6++6pkyz1cHDhzgvPPOY/bs2axbt47XXnuNK6+8ssHc3KNHj2b48OG88847DBkyhDFjxvj1OP4aNmwYp556Kvfccw9FRUWkpaXx5ptvNrgoYjQaeeGFFzj77LMZNmwY11xzDT169ODIkSOsWrWKpKQk/ve//3m237hxI0VFRZ6CgUII0amFqWq6EEKICKBPF/TDDz80u92CBQu0+Pj4Ju9//vnntbFjx2qxsbFaYmKiNmLECO3OO+/Ujh496tnG6XRqDz74oNatWzctNjZWmzp1qrZt2zatd+/ezU4Zpvvmm2+0GTNmaImJiVp8fLw2cuTIOlNRORwO7ZZbbtHS09M1g8FQZ/ow6k0ZpmmatmnTJm3WrFlaQkKCFhcXp02bNk1bu3atT3+fptrYlH//+98aoCUmJmrV1dV17tu/f7927bXXav3799diYmK0tLQ0bdq0adqKFSt8OrauqSnDbrrppka3Lyws1C666CItLi5OS01N1W688UZt27ZtDaYM0zRN27dvnzZ//nwtKytLs1gsWo8ePbS5c+dq7777bovtKigo0Mxms/bqq6/6/Zztdrv24IMPan379tUsFouWnZ2t3XPPPXWmL9M0NWXYOeec0+jj79y5UzvjjDO02NjYBtPTFRQUaDfddJOWnZ2tWSwWLSsrSzvrrLO0559/vs4xcnJytPPOO0+Li4vTunbtqt16662eafF8nTLs559/1i6++GItMTFRS01N1W6++eYGrwXd3//+dw3QHnnkkWaP3ZSmpgx79NFHG91+37592vTp07Xo6GgtMzNTu/fee7Xly5c3+vw2b96sXXjhhVqXLl206OhorXfv3tqll16qffnll3W2u+uuu7RevXrVmV5MCCE6K4OmtVAFRgghhBCiFa677jp2797tqcotmvfkk09y++23c/DgwQaV89sDq9VKnz59uPvuu7n11lvD3RwhhAg7CbqFEEIIEVK5ubmcdNJJfPnll0yePDnczYlomqYxatQounTp4pnnvr1ZsmQJjzzyCHv27CE6OjrczRFCiLCToFsIIYQQIswqKyv56KOPWLVqFf/+97/58MMPOe+888LdLCGEEEEgQbcQQgghRJgdPHiQvn37kpKSwm9/+1v+3//7f+FukhBCiCCRoFsIIYQQQgghhAgRmadb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1OKu56WXXmLo0KGedTbHjh3L559/7nle/RNcHn/8cSwWC7fddpvnMfVR4Dz44INYLJYG/3JycjzPq2+Cw969e/nFL35BUlISERERDBkyhGXLlnme12eFwMnKyjrqGrJYLNx0002ArqFAczqd3HfffWRnZxMREUGfPn14+OGHqV+eLJivHwXdndy//vUv7rjjDh544AF++OEHhg0bxqRJkzhw4ECgm9bllJWVMWzYMF544YVGn3/yySd57rnnePnll1myZAlRUVFMmjSJysrKDm5p1zR37lxuuukmFi9ezOzZs3E4HJx11lmUlZV59rn99tv573//y3vvvcfcuXPZt28fF110UQBb3bX06NGDxx9/nOXLl7Ns2TLOOOMMpkyZwo8//giof4LJ0qVL+dvf/sbQoUMbPK4+CqxBgwaxf/9+z7/58+d7nlPfBF5BQQHjxo3Dbrfz+eefs27dOp5++mkSEhI8++izQuAsXbq0wfUze/ZsAC655BJA11CgPfHEE7z00kv89a9/Zf369TzxxBM8+eSTPP/88559gvr6MaRTGz16tHHTTTd57judTiMjI8N47LHHAtgqAYwPPvjAc9/lchlpaWnGn/70J89jhYWFRlhYmPH2228HoIVy4MABAzDmzp1rGIbZH3a73Xjvvfc8+6xfv94AjEWLFgWqmV1eQkKC8eqrr6p/gkhJSYnRr18/Y/bs2cZpp51m3HrrrYZh6BoKtAceeMAYNmxYo8+pb4LD3XffbZx88slNPq/PCsHl1ltvNfr06WO4XC5dQ0Hg3HPPNa6++uoGj1100UXGtGnTDMMI/utHI92dWHV1NcuXL2fixImex6xWKxMnTmTRokUBbJkcafv27eTm5jboq7i4OMaMGaO+CpCioiIAEhMTAVi+fDkOh6NBH+Xk5JCZmak+CgCn08k777xDWVkZY8eOVf8EkZtuuolzzz23QV+ArqFgsHnzZjIyMujduzfTpk1j165dgPomWHz88cccf/zxXHLJJaSkpDBixAj+/ve/e57XZ4XgUV1dzZtvvsnVV1+NxWLRNRQETjrpJL755hs2bdoEwKpVq5g/fz5nn302EPzXT0igGyBtd+jQIZxOJ6mpqQ0eT01NZcOGDQFqlTQmNzcXoNG+cj8nHcflcnHbbbcxbtw4Bg8eDJh9FBoaSnx8fIN91Ucda82aNYwdO5bKykqio6P54IMPGDhwICtXrlT/BIF33nmHH374gaVLlx71nK6hwBozZgwzZ86kf//+7N+/n4ceeohTTjmFtWvXqm+CxLZt23jppZe44447+N3vfsfSpUu55ZZbCA0N5corr9RnhSDy4YcfUlhYyPTp0wH9fQsG99xzD8XFxeTk5GCz2XA6nTz66KNMmzYNCP7P2gq6RaTLuemmm1i7dm2D+Y4SHPr378/KlSspKiri/fff58orr2Tu3LmBbpYAu3fv5tZbb2X27NmEh4cHujlyBPdoD8DQoUMZM2YMvXr14t133yUiIiKALRM3l8vF8ccfzx//+EcARowYwdq1a3n55Ze58sorA9w6qe+1117j7LPPJiMjI9BNkVrvvvsus2bN4q233mLQoEGsXLmS2267jYyMjE5x/Si9vBPr1q0bNpvtqMqJeXl5pKWlBahV0hh3f6ivAu/mm2/mk08+4bvvvqNHjx6ex9PS0qiurqawsLDB/uqjjhUaGkrfvn0ZNWoUjz32GMOGDeMvf/mL+icILF++nAMHDjBy5EhCQkIICQlh7ty5PPfcc4SEhJCamqo+CiLx8fEcd9xxbNmyRddPkEhPT2fgwIENHhswYIBnGoA+KwSHnTt38vXXX3Pttdd6HtM1FHi//e1vueeee/jZz37GkCFDuPzyy7n99tt57LHHgOC/fhR0d2KhoaGMGjWKb775xvOYy+Xim2++YezYsQFsmRwpOzubtLS0Bn1VXFzMkiVL1FcdxDAMbr75Zj744AO+/fZbsrOzGzw/atQo7HZ7gz7auHEju3btUh8FkMvloqqqSv0TBCZMmMCaNWtYuXKl59/xxx/PtGnTPLfVR8GjtLSUrVu3kp6erusnSIwbN+6opSo3bdpEr169AH1WCBYzZswgJSWFc8891/OYrqHAKy8vx2ptGLrabDZcLhfQCa6fQFdyk/Z55513jLCwMGPmzJnGunXrjOuvv96Ij483cnNzA920LqekpMRYsWKFsWLFCgMwnnnmGWPFihXGzp07DcMwjMcff9yIj483PvroI2P16tXGlClTjOzsbKOioiLALe8abrzxRiMuLs6YM2eOsX//fs+/8vJyzz6//OUvjczMTOPbb781li1bZowdO9YYO3ZsAFvdtdxzzz3G3Llzje3btxurV6827rnnHsNisRhfffWVYRjqn2BUv3q5YaiPAunOO+805syZY2zfvt1YsGCBMXHiRKNbt27GgQMHDMNQ3wSD77//3ggJCTEeffRRY/PmzcasWbOMyMhI48033/Tso88KgeV0Oo3MzEzj7rvvPuo5XUOBdeWVVxrdu3c3PvnkE2P79u3Gf/7zH6Nbt27GXXfd5dknmK8fBd3HgOeff97IzMw0QkNDjdGjRxuLFy8OdJO6pO+++84Ajvp35ZVXGoZhLmVw3333GampqUZYWJgxYcIEY+PGjYFtdBfSWN8AxowZMzz7VFRUGL/61a+MhIQEIzIy0rjwwguN/fv3B67RXczVV19t9OrVywgNDTWSk5ONCRMmeAJuw1D/BKMjg271UeBceumlRnp6uhEaGmp0797duPTSS40tW7Z4nlffBIf//ve/xuDBg42wsDAjJyfHeOWVVxo8r88KgfXll18aQKPvua6hwCouLjZuvfVWIzMz0wgPDzd69+5t/N///Z9RVVXl2SeYrx+LYRhGQIbYRURERERERI5xmtMtIiIiIiIi4icKukVERERERET8REG3iIiIiIiIiJ8o6BYRERERERHxEwXdIiIiIiIiIn6ioFtERERERETETxR0i4iIiIiIiPiJgm4RERERERERP1HQLSIiEsQsFgsffvhhoJvBgw8+yPDhw/1+nvz8fFJSUtixY4ffz+UrWVlZPPvsswBUV1eTlZXFsmXLAtsoEREJGgq6RUSkSzt48CA33ngjmZmZhIWFkZaWxqRJk1iwYEGgm+YTO3bswGKxsHLlykA3xSuPPvooU6ZMISsry6/nqR8o+1JoaCi/+c1vuPvuu31+bBER6ZxCAt0AERGRQJo6dSrV1dW8/vrr9O7dm7y8PL755hvy8/MD3bQup7y8nNdee40vv/zSb+eorq4mNDTUb8cHmDZtGnfeeSc//vgjgwYN8uu5REQk+GmkW0REuqzCwkLmzZvHE088wemnn06vXr0YPXo09957Lz/5yU88+z3zzDMMGTKEqKgoevbsya9+9StKS0s9z8+cOZP4+Hg++eQT+vfvT2RkJBdffDHl5eW8/vrrZGVlkZCQwC233ILT6fS8Lisri4cffpif//znREVF0b17d1544YVm27x7925++tOfEh8fT2JiIlOmTGlVKvacOXOwWCx88803HH/88URGRnLSSSexcePGBvs9/vjjpKamEhMTwzXXXENlZeVRx3r11VcZMGAA4eHh5OTk8OKLL3qeu/rqqxk6dChVVVWAGeyOGDGCK664osm2ffbZZ4SFhXHiiSd6HisoKGDatGkkJycTERFBv379mDFjhuf5NWvWcMYZZxAREUFSUhLXX399g76ZPn06F1xwAY8++igZGRn079+f8ePHs3PnTm6//XYsFgsWi8Wz//z58znllFOIiIigZ8+e3HLLLZSVlXmeP3DgAOeffz4RERFkZ2cza9aso36OhIQExo0bxzvvvNPkzyoiIl2Hgm4REemyoqOjiY6O5sMPP/QEh42xWq0899xz/Pjjj7z++ut8++233HXXXQ32KS8v57nnnuOdd97hiy++YM6cOVx44YV89tlnfPbZZ7zxxhv87W9/4/3332/wuj/96U8MGzaMFStWcM8993Drrbcye/bsRtvhcDiYNGkSMTExzJs3jwULFhAdHc3kyZOprq5u1c/+f//3fzz99NMsW7aMkJAQrr76as9z7777Lg8++CB//OMfWbZsGenp6Q0CaoBZs2Zx//338+ijj7J+/Xr++Mc/ct999/H6668D8Nxzz1FWVsY999zjOV9hYSF//etfm2zTvHnzGDVqVIPH7rvvPtatW8fnn3/O+vXreemll+jWrRsAZWVlTJo0iYSEBJYuXcp7773H119/zc0339zgGN988w0bN25k9uzZfPLJJ/znP/+hR48e/OEPf2D//v3s378fgK1btzJ58mSmTp3K6tWr+de//sX8+fMbHG/69Ons3r2b7777jvfff58XX3yRAwcOHPWzjB49mnnz5rXYDyIi0gUYIiIiXdj7779vJCQkGOHh4cZJJ51k3HvvvcaqVauafc17771nJCUlee7PmDHDAIwtW7Z4HrvhhhuMyMhIo6SkxPPYpEmTjBtuuMFzv1evXsbkyZMbHPvSSy81zj77bM99wPjggw8MwzCMN954w+jfv7/hcrk8z1dVVRkRERHGl19+2Whbt2/fbgDGihUrDMMwjO+++84AjK+//tqzz6effmoARkVFhWEYhjF27FjjV7/6VYPjjBkzxhg2bJjnfp8+fYy33nqrwT4PP/ywMXbsWM/9hQsXGna73bjvvvuMkJAQY968eY220W3KlCnG1Vdf3eCx888/37jqqqsa3f+VV14xEhISjNLS0gY/i9VqNXJzcw3DMIwrr7zSSE1NNaqqqhq8tlevXsaf//znBo9dc801xvXXX9/gsXnz5hlWq9WoqKgwNm7caADG999/73l+/fr1BnDUsf7yl78YWVlZzf68IiLSNWikW0REurSpU6eyb98+Pv74YyZPnsycOXMYOXIkM2fO9Ozz9ddfM2HCBLp3705MTAyXX345+fn5lJeXe/aJjIykT58+nvupqalkZWURHR3d4LEjR0XHjh171P3169c32tZVq1axZcsWYmJiPKP0iYmJVFZWsnXr1lb93EOHDvXcTk9PB/C0bf369YwZM6bJdpaVlbF161auueYaTzuio6N55JFHGrRj7Nix/OY3v+Hhhx/mzjvv5OSTT262TRUVFYSHhzd47MYbb+Sdd95h+PDh3HXXXSxcuNDz3Pr16xk2bBhRUVGex8aNG4fL5WqQLj9kyBCv5nGvWrWKmTNnNviZJk2ahMvlYvv27axfv56QkJAGo/E5OTnEx8cfdayIiIgGvx8iItJ1qZCaiIh0eeHh4Zx55pmceeaZ3HfffVx77bU88MADTJ8+nR07dnDeeedx44038uijj5KYmMj8+fO55pprqK6uJjIyEgC73d7gmBaLpdHHXC5Xm9tZWlrKqFGjGp1HnJyc3Kpj1W+be06zt21zz5n++9//flRwbrPZPLddLhcLFizAZrOxZcuWFo/brVs3CgoKGjx29tlns3PnTj777DNmz57NhAkTuOmmm3jqqae8aivQIChvTmlpKTfccAO33HLLUc9lZmayadMmr895+PDhVveJiIgcmzTSLSIicoSBAwd6imctX74cl8vF008/zYknnshxxx3Hvn37fHauxYsXH3V/wIABje47cuRINm/eTEpKCn379m3wLy4uzmdtGjBgAEuWLGmynampqWRkZLBt27aj2pGdne3Z709/+hMbNmxg7ty5fPHFFw0KoDVmxIgRrFu37qjHk5OTufLKK3nzzTd59tlneeWVVzztXLVqVYNCZwsWLMBqtdK/f/9mzxUaGtqgqB2Y7++6deuO+pn69u1LaGgoOTk51NTUsHz5cs9rNm7cSGFh4VHHX7t2LSNGjGi2DSIi0jUo6BYRkS4rPz+fM844gzfffJPVq1ezfft23nvvPZ588kmmTJkCQN++fXE4HDz//PNs27aNN954g5dfftlnbViwYAFP4sYLEAAAA2pJREFUPvkkmzZt4oUXXuC9997j1ltvbXTfadOm0a1bN6ZMmcK8efPYvn07c+bM4ZZbbmHPnj0+a9Ott97KP/7xD2bMmMGmTZt44IEH+PHHHxvs89BDD/HYY4/x3HPPsWnTJtasWcOMGTN45plnAFixYgX3338/r776KuPGjeOZZ57h1ltvZdu2bU2ed9KkSfz4448NRrvvv/9+PvroI7Zs2cKPP/7IJ5984vlSYtq0aYSHh3PllVeydu1avvvuO379619z+eWXk5qa2uzPmJWVxf/+9z/27t3LoUOHALj77rtZuHAhN998MytXrmTz5s189NFHnkJq/fv3Z/Lkydxwww0sWbKE5cuXc+211xIREXHU8efNm8dZZ53lxbstIiLHOgXdIiLSZUVHRzNmzBj+/Oc/c+qppzJ48GDuu+8+rrvuOk+V7WHDhvHMM8/wxBNPMHjwYGbNmsVjjz3mszbceeedLFu2jBEjRvDII4/wzDPPMGnSpEb3jYyM5H//+x+ZmZlcdNFFDBgwwLOcV2xsrM/adOmll3Lfffdx1113MWrUKHbu3MmNN97YYJ9rr72WV199lRkzZjBkyBBOO+00Zs6cSXZ2NpWVlfziF79g+vTpnH/++QBcf/31nH766Vx++eVHjTC7DRkyhJEjR/Luu+96HgsNDeXee+9l6NChnHrqqdhsNs9SXJGRkXz55ZccPnyYE044gYsvvpgJEyY0WyHd7Q9/+AM7duygT58+njTwoUOHMnfuXDZt2sQpp5zCiBEjuP/++8nIyPC8bsaMGWRkZHDaaadx0UUXcf3115OSktLg2IsWLaKoqIiLL77Yi3dbRESOdRbDMIxAN0JERKQrysrK4rbbbuO2224LdFOCxqeffspvf/tb1q5di9XaOccGLr30UoYNG8bvfve7QDdFRESCgAqpiYiISNA499xz2bx5M3v37qVnz56Bbk6rVVdXM2TIEG6//fZAN0VERIKEgm4REREJKp155D80NJTf//73gW6GiIgEEaWXi4iIiIiIiPhJ55wsJSIiIiIiItIJKOgWERERERER8RMF3SIiIiIiIiJ+oqBbRERERERExE8UdIuIiIiIiIj4iYJuERERERERET9R0C0iIiIiIiLiJwq6RURERERERPxEQbeIiIiIiIiIn/w/1aTAa4UczGsAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Test MSE: 0.374343\n", - "R² Score: 0.6654\n", - "Epoch 101 | Loss: 0.032393\n", - "Epoch 102 | Loss: 0.029607\n", - "Epoch 103 | Loss: 0.028110\n", - "Epoch 104 | Loss: 0.030553\n", - "Epoch 105 | Loss: 0.029351\n", - "Epoch 106 | Loss: 0.024944\n", - "Epoch 107 | Loss: 0.024700\n", - "Epoch 108 | Loss: 0.030539\n", - "Epoch 109 | Loss: 0.031320\n", - "Epoch 110 | Loss: 0.031327\n", - "Epoch 111 | Loss: 0.030809\n", - "Epoch 112 | Loss: 0.029617\n", - "Epoch 113 | Loss: 0.032479\n", - "Epoch 114 | Loss: 0.030788\n", - "Epoch 115 | Loss: 0.066005\n", - "Epoch 116 | Loss: 0.061317\n", - "Epoch 117 | Loss: 0.033944\n", - "Epoch 118 | Loss: 0.027432\n", - "Epoch 119 | Loss: 0.036940\n", - "Epoch 120 | Loss: 0.055142\n", - "Epoch 121 | Loss: 0.035384\n", - "Epoch 122 | Loss: 0.033386\n", - "Epoch 123 | Loss: 0.032751\n", - "Epoch 124 | Loss: 0.028801\n", - "Epoch 125 | Loss: 0.022487\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAHqCAYAAAAZLi26AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd3hb1fnA8a+W5T3jlcRxnD3IDoEkQAJkEQKEPRt2admElvVrGaUlHUDTUkqgFMIss4wyM0hYYWWRQfZylne8bc37++PoytuWbMmS7ffzPH7utXTHka9s673vOe8xaJqmIYQQQgghhBBCiIAzhroBQgghhBBCCCFEdyVBtxBCCCGEEEIIESQSdAshhBBCCCGEEEEiQbcQQgghhBBCCBEkEnQLIYQQQgghhBBBIkG3EEIIIYQQQggRJBJ0CyGEEEIIIYQQQSJBtxBCCCGEEEIIESQSdAshhBBCCCGEEEEiQbcQQoiQ6t+/P1dddZX3+9WrV2MwGFi9enXAzmEwGHjwwQcDdjzhn4MHDxIZGcnXX38d6qYEjK/v0wcffBCDwUBRUVHnNCwMLFmyhH79+mGz2ULdFCGECAsSdAshRA+2dOlSDAaD9ysyMpIhQ4Zw8803k5+fH+rm+eWjjz7qMYG1HvD58hUOfve733HCCScwderUTj/3I488wrvvvtvp5+0sjX+HW/rq379/p7Xpqquuwm638/TTT3faOYUQIpyZQ90AIYQQofe73/2OnJwcamtr+eqrr3jqqaf46KOP2LJlC9HR0Z3allNOOYWamhoiIiL82u+jjz7iySefbDbwrqmpwWzuPv/yhg8fzksvvdTgsXvvvZfY2Fj+7//+L0Stal5hYSEvvPACL7zwQkjO/8gjj3DBBRcwf/78kJw/2E455ZQm74XrrruOSZMm8fOf/9z7WGxsbKe1KTIykiuvvJLHH3+cW265JWxu/gghRKh0n08gQggh2u2MM85g4sSJgPrAnpKSwuOPP857773HpZde2uw+VVVVxMTEBLwtRqORyMjIgB4z0McLtfT0dK644ooGj/3xj3+kV69eTR6vz+12Y7fbO/Xn8fLLL2M2mznrrLM67ZyaplFbW0tUVFSnnTNUBgwYwIABAxo89otf/IIBAwa0+l5wOp243W6/b2756qKLLuLPf/4zq1at4rTTTgvKOYQQoquQ7uVCCCGa0D8k79u3D1DdRWNjY9mzZw9z584lLi6Oyy+/HFCB3OLFixk5ciSRkZGkp6dzww03cOzYsQbH1DSN3//+9/Tt25fo6GhOPfVUtm7d2uTcLY2V/e6775g7dy5JSUnExMQwevRo/va3v3nb9+STTwI027W6uTHdGzZs4IwzziA+Pp7Y2FhOP/10vv322wbb6F13v/76axYuXEhqaioxMTGce+65FBYWtvozfPTRRzEYDBw4cKDJc/feey8RERHen9GuXbs4//zzycjIIDIykr59+3LJJZdQVlbW6jnaYjAYuPnmm3nllVcYOXIkVquVTz75pMWf8f79+zEYDCxdurTB49u3b+eCCy4gOTmZyMhIJk6cyPvvv+9TG959911OOOGEJplWX16z0+nk4YcfZuDAgVitVvr37899993XZKxw//79mTdvHp9++ikTJ04kKiqKp59+GoPBQFVVFS+88IL3PVG/fsDhw4e55pprSE9Px2q1MnLkSJ577rkmr+HQoUPMnz+fmJgY0tLSuOOOO/wer1xUVMRFF11EfHw8KSkp3HbbbdTW1nqfnzZtGmPGjGl236FDhzJ79my/zleffl0fffRRFi9e7P15/vTTT973+P79+xvs09rv4Zw5c0hISCA6Oppp06Y1O1Z/woQJJCcn895777W73UII0V1IplsIIUQTe/bsASAlJcX7mNPpZPbs2Zx00kk8+uij3m7nN9xwA0uXLuXqq6/m1ltvZd++ffzjH/9gw4YNfP3111gsFgDuv/9+fv/73zN37lzmzp3L+vXrmTVrFna7vc32LF++nHnz5pGZmcltt91GRkYG27Zt44MPPuC2227jhhtu4MiRIyxfvrxJV9vmbN26lZNPPpn4+HjuuusuLBYLTz/9NNOnT+fzzz/nhBNOaLD9LbfcQlJSEg888AD79+9n8eLF3Hzzzbz++ustnuOiiy7irrvu4o033uDXv/51g+feeOMNZs2aRVJSEna7ndmzZ2Oz2bjlllvIyMjg8OHDfPDBB5SWlpKQkNDm62nNZ599xhtvvMHNN99Mr1696N+/P6WlpT7vv3XrVqZOnUqfPn245557iImJ4Y033mD+/Pm8/fbbnHvuuS3u63A4+OGHH/jlL3/Z4HFfX/N1113HCy+8wAUXXMCdd97Jd999x6JFi9i2bRvvvPNOg2Pu2LGDSy+9lBtuuIHrr7+eoUOH8tJLLzXpaj1w4EAA8vPzOfHEE703JlJTU/n444+59tprKS8v5/bbbwfU0ITTTz+d3Nxcbr31Vnr37s1LL73EZ5995vPPENT7oX///ixatIhvv/2Wv//97xw7dowXX3wRgJ/97Gdcf/31bNmyheOOO8673w8//MDOnTv5zW9+49f5mvP8889TW1vLz3/+c6xWK8nJyX7t/9lnn3HGGWcwYcIEHnjgAYxGI88//zynnXYaX375JZMmTWqw/fjx47tV8TwhhGg3TQghRI/1/PPPa4C2YsUKrbCwUDt48KD22muvaSkpKVpUVJR26NAhTdM07corr9QA7Z577mmw/5dffqkB2iuvvNLg8U8++aTB4wUFBVpERIR25plnam6327vdfffdpwHalVde6X1s1apVGqCtWrVK0zRNczqdWk5Ojpadna0dO3aswXnqH+umm27SWvq3BmgPPPCA9/v58+drERER2p49e7yPHTlyRIuLi9NOOeWUJj+fGTNmNDjXHXfcoZlMJq20tLTZ8+kmT56sTZgwocFj33//vQZoL774oqZpmrZhwwYN0N58881Wj9WWkSNHatOmTWvwGKAZjUZt69atDR5v/DPW7du3TwO0559/3vvY6aefro0aNUqrra31PuZ2u7UpU6ZogwcPbrVNu3fv1gDtiSeeaPC4L69548aNGqBdd911DR7/1a9+pQHaZ5995n0sOztbA7RPPvmkyXFiYmIavL901157rZaZmakVFRU1ePySSy7REhIStOrqak3TNG3x4sUaoL3xxhvebaqqqrRBgwY1+zNs7IEHHtAA7eyzz27w+I033qgB2o8//qhpmqaVlpZqkZGR2t13391gu1tvvVWLiYnRKisrWz1PfY1fs35d4+PjtYKCggbb6u/xffv2NXi88XvE7XZrgwcP1mbPnt3gd6G6ulrLycnRZs6c2aQdP//5z7WoqCif2y2EEN2VdC8XQgjBjBkzSE1NJSsri0suuYTY2Fjeeecd+vTp02C7xhnLN998k4SEBGbOnElRUZH3a8KECcTGxrJq1SoAVqxYgd1ub1JUSc8mtmbDhg3s27eP22+/ncTExAbPtadAk8vlYtmyZcyfP7/BWNjMzEwuu+wyvvrqK8rLyxvs8/Of/7zBuU4++WRcLlezXcfru/jii1m3bp235wDA66+/jtVq5ZxzzgHwZnU//fRTqqur/X49bZk2bRojRoxo174lJSV89tlnXHTRRVRUVHivb3FxMbNnz2bXrl0cPny4xf2Li4sBSEpKavC4L6/5o48+AmDhwoUNHr/zzjsB+PDDDxs8npOT43MXbE3TePvttznrrLPQNK3Be3f27NmUlZWxfv16bzsyMzO54IILvPtHR0c3KFLmi5tuuqnB97fccov3+KB+Jueccw7/+c9/0DQNUO/V119/3du1vaPOP/98UlNT27Xvxo0b2bVrF5dddhnFxcXen1dVVRWnn346X3zxBW63u8E+SUlJ1NTUBOV9LYQQXYkE3UIIIXjyySdZvnw5q1at4qeffmLv3r1NAhiz2Uzfvn0bPLZr1y7KyspIS0sjNTW1wVdlZSUFBQUA3uB08ODBDfZPTU1tEpA1pges9bvcdkRhYSHV1dUMHTq0yXPDhw/H7XZz8ODBBo/369evwfd6mxuPW2/swgsvxGg0eruha5rGm2++6R1LDipYXLhwIc8++yy9evVi9uzZPPnkkx0ez63Lyclp9767d+9G0zR++9vfNrm+DzzwAID3GrdGDyLrt6mt13zgwAGMRiODBg1qsG9GRgaJiYlNbnj48zoLCwspLS3lmWeeafK6rr766gav68CBAwwaNKjJDZ7m3j+tafzeHzhwIEajscFY6gULFpCbm8uXX34JqJtV+fn5/OxnP/PrXC3pyHth165dAFx55ZVNfmbPPvssNputyXtWv+5SvVwI0dPJmG4hhBBMmjTJW728JVarFaOx4b1at9tNWloar7zySrP7tDerFm5MJlOzjzcOJhvr3bs3J598Mm+88Qb33Xcf3377Lbm5ufzpT39qsN1jjz3GVVddxXvvvceyZcu49dZbvWN/G9/o8FdzFbxbCoJcLleD7/XM5a9+9asWs8iNg+L69JoAzd2c8PU1+xqw+VOpXH9dV1xxBVdeeWWz24wePdrn47VHc69r9uzZpKen8/LLL3PKKafw8ssvk5GRwYwZMwJyzkC8F/7yl78wduzYZvdpXCzv2LFjREdH94gq8kII0RoJuoUQQrTbwIEDWbFiBVOnTm31g3V2djagsmX1u3QXFha2mS3WC19t2bKl1eDD1+AsNTWV6OhoduzY0eS57du3YzQaycrK8ulYvrj44ou58cYb2bFjB6+//jrR0dHNTp81atQoRo0axW9+8xvWrFnD1KlTWbJkCb///e8D1hadnqlvXFCtcfZYv1YWi6VdgV+/fv2IioryVsFvrLXXnJ2djdvtZteuXQwfPty7T35+PqWlpd73VFuae1+kpqYSFxeHy+Vq83VlZ2ezZcsWNE1rcKzm3j+t2bVrV4NM8+7du3G73fTv39/7mMlk4rLLLmPp0qX86U9/4t133+X6669v8aZPIPj6XtB/D+Pj431+L+zbt6/BtRNCiJ5KupcLIYRot4suugiXy8XDDz/c5Dmn0+n9ID9jxgwsFgtPPPFEg+zw4sWL2zzH+PHjycnJYfHixU0Cg/rH0se8tlWZ22QyMWvWLN57770GXXvz8/N59dVXOemkk7xdvwPh/PPPx2Qy8Z///Ic333yTefPmNRifW15ejtPpbLDPqFGjMBqNfk9L5avs7GxMJhNffPFFg8f/+c9/Nvg+LS2N6dOn8/TTT3P06NEmx2lr2jSLxcLEiRNZu3Ztg8d9ec1z584Fmr5HHn/8cQDOPPPMVs+ti4mJafKeMJlMnH/++bz99tts2bKlyT71X9fcuXM5cuQIb731lvex6upqnnnmGZ/Or9OntNM98cQTAJxxxhkNHv/Zz37GsWPHuOGGG6isrGx1ru1A0IPp+u8Fl8vV5PVNmDCBgQMH8uijj1JZWdnkOM29F9avX8+UKVMC3GIhhOh6JNMthBCi3aZNm8YNN9zAokWL2LhxI7NmzcJisbBr1y7efPNN/va3v3HBBReQmprKr371KxYtWsS8efOYO3cuGzZs4OOPP6ZXr16tnsNoNPLUU09x1llnMXbsWK6++moyMzPZvn07W7du5dNPPwVUUABw6623Mnv2bEwmE5dcckmzx/z973/P8uXLOemkk7jxxhsxm808/fTT2Gw2/vznPwf0Z5SWlsapp57K448/TkVFBRdffHGD5z/77DNuvvlmLrzwQoYMGYLT6eSll17yBobBkJCQwIUXXsgTTzyBwWBg4MCBfPDBB82Oz37yySc56aSTGDVqFNdffz0DBgwgPz+fb775hkOHDvHjjz+2eq5zzjmH//u//6O8vNx7M8OX1zxmzBiuvPJKnnnmGUpLS5k2bRrff/89L7zwAvPnz+fUU0/16bVOmDCBFStW8Pjjj9O7d29ycnI44YQT+OMf/8iqVas44YQTuP766xkxYgQlJSWsX7+eFStWUFJSAsD111/PP/7xDxYsWMC6devIzMzkpZde8k6Z56t9+/Zx9tlnM2fOHL755htefvllLrvssiZzc48bN47jjjuON998k+HDhzN+/Hi/zuOvkSNHcuKJJ3LvvfdSUlJCcnIyr732WpObIkajkWeffZYzzjiDkSNHcvXVV9OnTx8OHz7MqlWriI+P53//+593+3Xr1lFSUuItGCiEED1aiKqmCyGECAP6dEE//PBDq9tdeeWVWkxMTIvPP/PMM9qECRO0qKgoLS4uThs1apR21113aUeOHPFu43K5tIceekjLzMzUoqKitOnTp2tbtmzRsrOzW50yTPfVV19pM2fO1OLi4rSYmBht9OjRDaaicjqd2i233KKlpqZqBoOhwfRhNJoyTNM0bf369drs2bO12NhYLTo6Wjv11FO1NWvW+PTzaamNLfnXv/6lAVpcXJxWU1PT4Lm9e/dq11xzjTZw4EAtMjJSS05O1k499VRtxYoVPh1b19KUYTfddFOz2xcWFmrnn3++Fh0drSUlJWk33HCDtmXLliZThmmapu3Zs0dbsGCBlpGRoVksFq1Pnz7avHnztLfeeqvNduXn52tms1l76aWX/H7NDodDe+ihh7ScnBzNYrFoWVlZ2r333ttg+jJNU1OGnXnmmc2ef/v27dopp5yiRUVFNZmeLj8/X7vpppu0rKwszWKxaBkZGdrpp5+uPfPMMw2OceDAAe3ss8/WoqOjtV69emm33Xabd1o8X6cM++mnn7QLLrhAi4uL05KSkrSbb765yXtB9+c//1kDtEceeaTVY7ekpSnD/vKXvzS7/Z49e7QZM2ZoVqtVS09P1+677z5t+fLlzb6+DRs2aOedd56WkpKiWa1WLTs7W7vooou0lStXNtju7rvv1vr169dgejEhhOipDJrWRhUYIYQQQogOuPbaa9m5c6e3Krdo3d/+9jfuuOMO9u/f36Ryfldgs9no378/99xzD7fddluomyOEECEnQbcQQgghgio3N5chQ4awcuVKpk6dGurmhDVN0xgzZgwpKSneee67miVLlvDII4+wa9curFZrqJsjhBAhJ0G3EEIIIUSIVVVV8f7777Nq1Sr+9a9/8d5773H22WeHullCCCECQIJuIYQQQogQ279/Pzk5OSQmJnLjjTfyhz/8IdRNEkIIESASdAshhBBCCCGEEEEi83QLIYQQQgghhBBBIkG3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2ev1GpmZmcbvf/97/2WFhYVGdHS08e9//zsMI5T9+/cbgLFw4ULDMMz5cLlcxmuvveY/Zv369QZgLFmyJFzD7PZ69OhhPPPMM5qfCFJSUmIMHTrUmD9/vnHSSScZN954o2EYeg+F2z333GOMHTu20es0N5Hh9ttvN44//vgmr9dnhchy4403GoMHDza8Xq/eQxHgzDPPNC6//PJ6l51zzjnGxRdfbBhG5L9/lOnuxKqrq1m+fDnTp0/3X2a325k+fTpLliwJ48ikoW3btpGfn19vrpKTk5k8ebLmKkyKiooASE1NBWD58uW43e56c5STk0O/fv00R2Hg8Xh4+eWXKSsrY8qUKZqfCHLddddx5pln1psL0HsoEmzatImsrCwGDRrExRdfzM6dOwHNTaR4++23mTRpEueddx49e/Zk/PjxPP300/7r9VkhclRXV/PCCy9w+eWXY7PZ9B6KAMcddxwLFixg48aNAKxatYrFixdzxhlnAJH//nGGewDSdgcPHsTj8dCrV696l/fq1YsNGzaEaVTSmPz8fIBG58q6TjqO1+vlpptuYurUqYwaNQow5ygqKoqUlJR6x2qOOtZ3333HlClTqKysJCEhgTfeeIMRI0awcuVKzU8EePnll/n2229ZunTpEdfpPRRekydPZt68eQwbNoy9e/dy3333ccIJJ7BmzRrNTYTYunUrTz75JLfccgu//OUvWbp0KTfccANRUVFcdtll+qwQQd58800KCwuZO3cuoL9vkeCOO+6guLiYnJwcHA4HHo+HBx54gIsvvhiI/M/aCrpFpNu57rrrWLNmTb31jhIZhg0bxsqVKykqKuL111/nsssuY+HCheEelgC7du3ixhtvZP78+cTExIR7ONKAle0BGDNmDJMnT6Z///68+uqrxMbGhnFkYvF6vUyaNInf/va3AIwfP541a9bw1FNPcdlll4V5dFLXP/7xD8444wyysrLCPRTxefXVV3nxxRd56aWXGDlyJCtXruSmm24iKyurU7x/VF7eiaWnp+NwOI7onLhv3z4yMzPDNCppjDUfmqvwu/7663n33Xf57LPP6Nu3r//yzMxMqqurKSwsrHe85qhjRUVFMWTIECZOnMiDDz7I2LFj+fOf/6z5iQDLly9n//79TJgwAafTidPpZOHChTz22GM4nU569eqlOYogKSkpHHXUUWzevFnvnwjRu3dvRowYUe+y4cOH+5cB6LNCZNixYweffPIJV155pf8yvYfC7xe/+AV33HEHP/zhDxk9ejSXXHIJN998Mw8++CAQ+e8fBd2dWFRUFBMnTmTBggX+y7xeLwsWLGDKlClhHJk0NHDgQDIzM+vNVXFxMV9//bXmqoMYhsH111/PG2+8waeffsrAgQPrXT9x4kRcLle9OcrNzWXnzp2aozDyer1UVVVpfiLAtGnT+O6771i5cqX/36RJk7j44ov95zVHkaO0tJQtW7bQu3dvvX8ixNSpU4/YqnLjxo30798f0GeFSPHss8/Ss2dPzjzzTP9leg+FX3l5OXZ7/dDV4XDg9XqBTvD+CXcnN2mfl19+2YiOjjbmzZtnrFu3zrj66quNlJQUIz8/P9xD63ZKSkqMFStWGCtWrDAA45FHHjFWrFhh7NixwzAMw/jd735npKSkGG+99ZaxevVqY/bs2cbAgQONioqKMI+8e7j22muN5ORk4/PPPzf27t3r/1deXu4/5ic/+YnRr18/49NPPzWWLVtmTJkyxZgyZUoYR9293HHHHcbChQuNbdu2GatXrzbuuOMOw2azGR9//LFhGJqfSFS3e7lhaI7C6dZbbzU+//xzY9u2bcYXX3xhTJ8+3UhPTzf2799vGIbmJhJ88803htPpNB544AFj06ZNxosvvmjExcUZL7zwgv8YfVYIL4/HY/Tr18+4/fbbj7hO76Hwuuyyy4w+ffoY7777rrFt2zbjv//9r5Genm7cdttt/mMi+f2joLsLePzxx41+/foZUVFRxjHHHGN89dVX4R5St/TZZ58ZwBH/LrvsMsMwzK0M7rrrLqNXr15GdHS0MW3aNCM3Nze8g+5GGpsbwHj22Wf9x1RUVBg//elPjR49ehhxcXHG2Wefbezduzd8g+5mLr/8cqN///5GVFSUkZGRYUybNs0fcBuG5icSNQy6NUfhc8EFFxi9e/c2oqKijD59+hgXXHCBsXnzZv/1mpvI8M477xijRo0yoqOjjZycHOPvf/97vev1WSG8PvroIwNo9DnXeyi8iouLjRtvvNHo16+fERMTYwwaNMj4v//7P6Oqqsp/TCS/f2yGYRhhSbGLiIiIiIiIdHFa0y0iIiIiIiISIgq6RUREREREREJEQbeIiIiIiIhIiCjoFhEREREREQkRBd0iIiIiIiIiIaKgW0RERERERCREFHSLiIiIiIiIhIiCbhEREREREZEQUdAtIiISwWw2G2+++Wa4h8G9997LuHHjQv44BQUF9OzZk+3bt4f8sYJlwIABPProowBUV1czYMAAli1bFt5BiYhIxFDQLSIi3dqBAwe49tpr6devH9HR0WRmZjJjxgy++OKLcA8tKLZv347NZmPlypXhHkqrPPDAA8yePZsBAwaE9HHqBsrBFBUVxc9//nNuv/32oN+3iIh0Ts5wD0BERCSc5syZQ3V1Nc899xyDBg1i3759LFiwgIKCgnAPrdspLy/nH//4Bx999FHIHqO6upqoqKiQ3T/AxRdfzK233sratWsZOXJkSB9LREQinzLdIiLSbRUWFrJo0SIeeughTjnlFPr3788xxxzDnXfeyfe//33/cY888gijR48mPj6e7OxsfvrTn1JaWuq/ft68eaSkpPDuu+8ybNgw4uLiOPfccykvL+e5555jwIAB9OjRgxtuuAGPx+O/3YABA7j//vu58MILiY+Pp0+fPjzxxBPNjnnXrl2cf/75pKSkkJqayuzZswMqxf7888+x2WwsWLCASZMmERcXx3HHHUdubm694373u9/Rq1cvEhMTueKKK6isrDzivp555hmGDx9OTEwMOTk5/PWvf/Vfd/nllzNmzBiqqqoAM9gdP348l156aZNje//994mOjubYY4/1X3b48GEuvvhiMjIyiI2NZejQoTz77LP+67/77jtOPfVUYmNjSUtL4+qrr643N3PnzuUHP/gBDzzwAFlZWQwbNoyTTz6ZHTt2cPPNN2Oz2bDZbP7jFy9ezAknnEBsbCzZ2dnccMMNlJWV+a/fv38/Z511FrGxsQwcOJAXX3zxiN+jR48eTJ06lZdffrnJ31VERLoPBd0iItJtJSQkkJCQwJtvvukPDhtjt9t57LHHWLt2Lc899xyffvopt912W71jysvLeeyxx3j55Zf58MMP+fzzzzn77LN5//33ef/99/nXv/7F3/72N15//fV6t/v973/P2LFjWbFiBXfccQc33ngj8+fPb3QcbrebGTNmkJiYyKJFi/jiiy9ISEhg5syZVFdXB/S7/9///R9//OMfWbZsGU6nk8svv9x/3auvvsq9997Lb3/7W5YtW0bv3r3rBdQAL774InfffTcPPPAA69ev57e//S133XUXzz33HACPPfYYZWVl3HHHHf7HKyws5C9/+UuTY1q0aBETJ06sd9ldd93FunXr+OCDD1i/fj1PPvkk6enpAJSVlTFjxgx69OjB0qVLee211/jkk0+4/vrr693HggULyM3NZf78+bz77rv897//pW/fvvz6179m79697N27F4AtW7Ywc+ZM5syZw+rVq3nllVdYvHhxvfubO3cuu3bt4rPPPuP111/nr3/9K/v37z/idznmmGNYtGhRi/MgIiLdgCEiItKNvf7660aPHj2MmJgY47jjjjPuvPNOY9WqVc3e5rXXXjPS0tL8Pz/77LMGYGzevNl/2TXXXGPExcUZJSUl/stmzJhhXHPNNf6f+/fvb8ycObPefV9wwQXGGWec4f8ZMN544w3DMAzjX//6lzFs2DDD6/X6r6+qqjJiY2ONjz76qNGxbtu2zQCMFStWGIZhGJ999pkBGJ988on/mPfee88AjIqKCsMwDGPKlCnGT3/603r3M3nyZGPs2LH+nwcPHmy89NJL9Y65//77jSlTpvh//vLLLw2Xy2XcddddhtPpNBYtWtToGC2zZ882Lr/88nqXnXXWWcaPf/zjRo//+9//bvTo0cMoLS2t97vY7XYjPz/fMAzDuOyyy4xevXoZVVVV9W7bv39/409/+lO9y6644grj6quvrnfZokWLDLvdblRUVBi5ubkGYHzzzTf+69evX28AR9zXn//8Z2PAgAHN/r4iItI9KNMtIiLd2pw5c9izZw9vv/02M2fO5PPPP2fChAnMmzfPf8wnn3zCtGnT6NOnD4mJiVxyySUUFBRQXl7uPyYuLo7Bgwf7f+7VqxcDBgwgISGh3mUNs6JTpkw54uf169c3OtZVq1axefNmEhMT/Vn61NRUKisr2bJlS0C/95gxY/zne/fuDeAf2/r165k8eXKT4ywrK2PLli1cccUV/nEkJCTwm9/8pt44pkyZws9//nPuv/9+br31Vo4//vhmx1RRUUFMTEy9y6699lpefvllxo0bx2233caXX37pv279+vWMHTuW+Ph4/2VTp07F6/XWK5cfPXp0q9Zxr1q1innz5tX7nWbMmIHX62Xbtm2sX78ep9NZLxufk5NDSkrKEfcVGxtb7/UhIiLdlxqpiYhItxcTE8Npp53Gaaedxl133cWVV17JPffcw9y5rRrDmAAABU1JREFUc9m+fTvf+973uPbaa3nggQdITU1l8eLFXHHFFVRXVxMXFweAy+Wqd582m63Ry7xeb5vHWVpaysSJExtdR5yRkRHQfdUdm7WmubVjs9ZMP/3000cE5w6Hw3/e6/XyxRdf4HA42Lx5c4v3m56ezuHDh+tddsYZZ7Bjxw7ef/995s+fz7Rp07juuuv4wx/+0KqxAvWC8uaUlpZyzTXXcMMNNxxxXb9+/di4cWOrH/PQoUMBz4mIiHRNynSLiIg0MGLECH/zrOXLl+P1evnjH//Isccey1FHHcWePXuC9lhfffXVET8PHz680WMnTJjApk2b6NmzJ0OGDKn3Lzk5OWhjGj58OF9//XWT4+zVqxdZWVls3br1iHEMHDjQf9zvf/97NmzYwMKFC/nwww/rNUBrzPjx41m3bt0Rl2dkZHDZZZfxwgsv8Oijj/L3v//dP85Vq1bVa3T2xRdfYLfbGTZsWLOPFRUVVa+pHZjP77p16474nYYMGUJUVBQ5OTnU1NSwfPly/21yc3MpLCw84v7XrFnD+PHjmx2DiIh0Dwq6RUSk2yooKODUU0/lhRdeYPXq1Wzbto3XXnuNhx9+mNmzZwMwZMgQ3G43jz/+OFu3buVf//oXTz31VNDG8MUXX/Dwww+zceNGnnjiCV577TVuvPHGRo+9+OKLSU9PZ/bs2SxatIht27bx+eefc8MNN7B79+6gjenGG2/kn//8J88++ywbN27knnvuYe3atfWOue+++3jwwQd57LHH2LhxI9999x3PPvssjzzyCAArVqzg7rvv5plnnmHq1Kk88sgj3HjjjWzdurXJx50xYwZr166tl+2+++67eeutt9i8eTNr167l3Xff9X8pcfHFFxMTE8Nll13GmjVr+Oyzz/jZz37GJZdcQq9evZr9HQcMGMD//vc/8vLyOHjwIAC33347X375Jddffz0rV65k06ZNvPXWW/5GasOGDWPmzJlcc801fP311yxfvpwrr7yS2NjYI+5/0aJFnH766a14tkVEpKtT0C0iIt1WQkICkydP5k9/+hMnnngio0aN4q677uKqq67yd9keO3YsjzzyCA899BCjRo3ixRdf5MEHHwzaGG699VaWLVvG+PHj+c1vfsMjjzzCjBkzGj02Li6O//3vf/Tr149zzjmH4cOH+7fzSkpKCtqYLrjgAu666y5uu+02Jk6cyI4dO7j22mvrHXPllVfyzDPP8OyzzzJ69GhOOukk5s2bx8CBA6msrORHP/oRc+fO5ayzzgLg6quv5pRTTuGSSy45IsNsGT16NBMmTODVV1/1XxYVFcWdd97JmDFjOPHEE3E4HP6tuOLi4vjoo484dOgQRx99NOeeey7Tpk1rtkO65de//jXbt29n8ODB/jLwMWPGsHDhQjZu3MgJJ5zA+PHjufvuu8nKyvLf7tlnnyUrK4uTTjqJc845h6uvvpqePXvWu+8lS5ZQVFTEueee24pnW0REujqbYRhGuAchIiLSHQ0YMICbbrqJm266KdxDiRjvvfcev/jFL1izZg12e+fMDVxwwQWMHTuWX/7yl+EeioiIRAA1UhMREZGIceaZZ7Jp0yby8vLIzs4O93ACVl1dzejRo7n55pvDPRQREYkQCrpFREQkonTmzH9UVBS/+tWvwj0MERGJICovFxEREREREQmRzrlYSkRERERERKQTUNAtIiIiIiIiEiIKukVERERERERCREG3iIiIiIiISIgo6BYREREREREJEQXdIiIiIiIiIiGioFtEREREREQkRBR0i4iIiIiIiISIgm4RERERERGREPl/Dgf8lRZIIk0AAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Test MSE: 0.406312\n", - "R² Score: 0.6368\n", - "Epoch 126 | Loss: 0.025907\n", - "Epoch 127 | Loss: 0.027972\n", - "Epoch 128 | Loss: 0.025494\n", - "Epoch 129 | Loss: 0.019651\n", - "Epoch 130 | Loss: 0.027020\n", - "Epoch 131 | Loss: 0.037656\n", - "Epoch 132 | Loss: 0.028049\n", - "Epoch 133 | Loss: 0.030038\n", - "Epoch 134 | Loss: 0.029521\n", - "Epoch 135 | Loss: 0.030721\n", - "Epoch 136 | Loss: 0.024325\n" - ] - }, - { - "ename": "KeyboardInterrupt", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[10], line 127\u001b[0m\n\u001b[1;32m 124\u001b[0m y_batch \u001b[38;5;241m=\u001b[39m y_batch\u001b[38;5;241m.\u001b[39mto(device)\u001b[38;5;241m.\u001b[39mfloat()\u001b[38;5;241m.\u001b[39mview(\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m1\u001b[39m) \u001b[38;5;66;03m# (B,1)\u001b[39;00m\n\u001b[1;32m 126\u001b[0m optimizer\u001b[38;5;241m.\u001b[39mzero_grad()\n\u001b[0;32m--> 127\u001b[0m pred \u001b[38;5;241m=\u001b[39m \u001b[43mmodel\u001b[49m\u001b[43m(\u001b[49m\u001b[43msample_views_list\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# (B,1)\u001b[39;00m\n\u001b[1;32m 128\u001b[0m loss \u001b[38;5;241m=\u001b[39m criterion(pred, y_batch)\n\u001b[1;32m 129\u001b[0m loss\u001b[38;5;241m.\u001b[39mbackward()\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1739\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1737\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs) \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m 1738\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1739\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1750\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1745\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m 1746\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m 1747\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m 1748\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m 1749\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1750\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1752\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m 1753\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n", - "Cell \u001b[0;32mIn[10], line 85\u001b[0m, in \u001b[0;36mQECRegressor.forward\u001b[0;34m(self, sample_views_list)\u001b[0m\n\u001b[1;32m 84\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mforward\u001b[39m(\u001b[38;5;28mself\u001b[39m, sample_views_list):\n\u001b[0;32m---> 85\u001b[0m z \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43membedder\u001b[49m\u001b[43m(\u001b[49m\u001b[43msample_views_list\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# (B, d_model)\u001b[39;00m\n\u001b[1;32m 86\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhead(z)\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1739\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1737\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs) \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m 1738\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1739\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1750\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1745\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m 1746\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m 1747\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m 1748\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m 1749\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1750\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1752\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m 1753\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n", - "File \u001b[0;32m/mnt/c/Imeperial_MRes_Files/QEC/BO_for_QECcodes/bayesian_optimization/chaincomplexembedding.py:1041\u001b[0m, in \u001b[0;36mChainComplexEmbedder.forward\u001b[0;34m(self, views_or_list, batch_splits)\u001b[0m\n\u001b[1;32m 1039\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m vn \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m views:\n\u001b[1;32m 1040\u001b[0m \u001b[38;5;28;01mcontinue\u001b[39;00m\n\u001b[0;32m-> 1041\u001b[0m outs_per_view[vn] \u001b[38;5;241m=\u001b[39m \u001b[43mper_view\u001b[49m\u001b[43m[\u001b[49m\u001b[43mvn\u001b[49m\u001b[43m]\u001b[49m\u001b[43m(\u001b[49m\u001b[43mH\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mviews\u001b[49m\u001b[43m[\u001b[49m\u001b[43mvn\u001b[49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1043\u001b[0m \u001b[38;5;66;03m# Cross-view aggregation -> new H (shapes are identical across views per type)\u001b[39;00m\n\u001b[1;32m 1044\u001b[0m H_new: Dict[\u001b[38;5;28mstr\u001b[39m, torch\u001b[38;5;241m.\u001b[39mTensor] \u001b[38;5;241m=\u001b[39m {}\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1739\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1737\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs) \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m 1738\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1739\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1750\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1745\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m 1746\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m 1747\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m 1748\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m 1749\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1750\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1752\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m 1753\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n", - "File \u001b[0;32m/mnt/c/Imeperial_MRes_Files/QEC/BO_for_QECcodes/bayesian_optimization/chaincomplexembedding.py:624\u001b[0m, in \u001b[0;36mChainComplexMessagePassingLayer.forward\u001b[0;34m(self, H, view)\u001b[0m\n\u001b[1;32m 622\u001b[0m x \u001b[38;5;241m=\u001b[39m H[t] \u001b[38;5;66;03m# (N_t_global, in_dim)\u001b[39;00m\n\u001b[1;32m 623\u001b[0m x_norm \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mln_in[t](x)\n\u001b[0;32m--> 624\u001b[0m proj_dst[t] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mres_proj\u001b[49m\u001b[43m[\u001b[49m\u001b[43mt\u001b[49m\u001b[43m]\u001b[49m\u001b[43m(\u001b[49m\u001b[43mx_norm\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# (N_t_global, out_dim)\u001b[39;00m\n\u001b[1;32m 626\u001b[0m \u001b[38;5;66;03m# Iterate relations\u001b[39;00m\n\u001b[1;32m 627\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m r \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrelations:\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1739\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1737\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs) \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m 1738\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1739\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/module.py:1750\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1745\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m 1746\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m 1747\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m 1748\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m 1749\u001b[0m \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1750\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1752\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m 1753\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n", - "File \u001b[0;32m~/miniconda3/envs/QEC2/lib/python3.10/site-packages/torch/nn/modules/linear.py:125\u001b[0m, in \u001b[0;36mLinear.forward\u001b[0;34m(self, input)\u001b[0m\n\u001b[1;32m 124\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mforward\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;28minput\u001b[39m: Tensor) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Tensor:\n\u001b[0;32m--> 125\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mF\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlinear\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mweight\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbias\u001b[49m\u001b[43m)\u001b[49m\n", - "\u001b[0;31mKeyboardInterrupt\u001b[0m: " - ] - } - ], + "outputs": [], "source": [ "import math\n", - "import numpy as np\n", "import torch\n", "import torch.nn as nn\n", "from torch.utils.data import DataLoader\n", - "import matplotlib.pyplot as plt\n", - "from bayesian_optimization.chaincomplexembedding import *\n", + "from bayesian_optimization.chaincomplexembedding import ChainComplexEmbedder\n", "@torch.no_grad()\n", "def show_results(model, data_loader, encoder: E, device: torch.device):\n", " model.eval()\n", @@ -1280,7 +718,7 @@ }, { "cell_type": "markdown", - "id": "71e8b05b", + "id": "9", "metadata": {}, "source": [ "# Test our embedding in Gaussian Process" @@ -1289,33 +727,17 @@ { "cell_type": "code", "execution_count": null, - "id": "617d8645", + "id": "10", "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "must be real number, not CSSCode", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[14], line 345\u001b[0m\n\u001b[1;32m 339\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m mse, r2, df, extras\n\u001b[1;32m 340\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m mse, r2, extras\n\u001b[1;32m 344\u001b[0m model \u001b[38;5;241m=\u001b[39m get_model_(\n\u001b[0;32m--> 345\u001b[0m \u001b[43mtorch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtensor\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtrain_dataset\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdtype\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtorch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfloat32\u001b[49m\u001b[43m)\u001b[49m,\n\u001b[1;32m 346\u001b[0m torch\u001b[38;5;241m.\u001b[39mtensor(train_dataset\u001b[38;5;241m.\u001b[39my, dtype\u001b[38;5;241m=\u001b[39mtorch\u001b[38;5;241m.\u001b[39mfloat32),\n\u001b[1;32m 347\u001b[0m kernel_type\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mmatern\u001b[39m\u001b[38;5;124m'\u001b[39m,\n\u001b[1;32m 348\u001b[0m mean_type\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mlinear\u001b[39m\u001b[38;5;124m'\u001b[39m,\n\u001b[1;32m 349\u001b[0m mean_input\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m128\u001b[39m,\n\u001b[1;32m 350\u001b[0m )\n\u001b[1;32m 352\u001b[0m _ \u001b[38;5;241m=\u001b[39m train_gpytorch_model_separate(\n\u001b[1;32m 353\u001b[0m model,\n\u001b[1;32m 354\u001b[0m torch\u001b[38;5;241m.\u001b[39mtensor(train_dataset\u001b[38;5;241m.\u001b[39mX, dtype\u001b[38;5;241m=\u001b[39mtorch\u001b[38;5;241m.\u001b[39mfloat32),\n\u001b[1;32m 355\u001b[0m torch\u001b[38;5;241m.\u001b[39mtensor(train_dataset\u001b[38;5;241m.\u001b[39my, dtype\u001b[38;5;241m=\u001b[39mtorch\u001b[38;5;241m.\u001b[39mfloat32),\n\u001b[1;32m 356\u001b[0m training_iter\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m200\u001b[39m\n\u001b[1;32m 357\u001b[0m )\n\u001b[1;32m 359\u001b[0m mse1, r21, extra1 \u001b[38;5;241m=\u001b[39m evaluate_gp_on_test(train_dataset, model, plot\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, return_df\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m, use_std\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n", - "\u001b[0;31mTypeError\u001b[0m: must be real number, not CSSCode" - ] - } - ], + "outputs": [], "source": [ "# ======= GP + NN Embedder for QEC =======\n", - "import math\n", - "import numpy as np\n", "import torch\n", "import torch.nn as nn\n", "import gpytorch\n", - "from gpytorch.mlls import ExactMarginalLogLikelihood\n", "from gpytorch.distributions import MultivariateNormal\n", "from gpytorch.kernels import RBFKernel, MaternKernel, SpectralMixtureKernel, ScaleKernel\n", "import matplotlib.pyplot as plt\n", - "import pandas as pd\n", "\n", "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", "\n", @@ -1528,7 +950,6 @@ "):\n", " import torch\n", " import gpytorch\n", - " import numpy as np\n", " import pandas as pd\n", " import matplotlib.pyplot as plt\n", "\n", @@ -1674,19 +1095,10 @@ }, { "cell_type": "code", - "execution_count": 11, - "id": "ad36439e", + "execution_count": null, + "id": "11", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Train MSE: 0.249584 | Train R2: 0.735525\n", - "Test MSE: 0.705363 | Test R2: 0.423769\n" - ] - } - ], + "outputs": [], "source": [ "print(f\"Train MSE: {mse1:.6f} | Train R2: {r21:.6f}\")\n", "print(f\"Test MSE: {mse2:.6f} | Test R2: {r22:.6f}\")" @@ -1694,18 +1106,10 @@ }, { "cell_type": "code", - "execution_count": 12, - "id": "2254720f", + "execution_count": null, + "id": "12", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'avg_per_point_pdf': 0.4129357933998108, 'avg_per_point_logpdf': -1.3659827709197998, 'total_multivariate_loglik': -79.96087646484375, 'avg_multivariate_loglik_per_point': -1.3326812744140626, 'total_multivariate_nll': 79.96087646484375, 'avg_multivariate_nll_per_point': 1.3326812744140626, 'avg_per_point_nll_independent': 1.3659827709197998, 'z_mean': -0.2602979242801666, 'z_std': 1.4013820886611938, 'z_mae': 1.1067477464675903, 'coverage_within_1sigma': 0.5333333611488342, 'coverage_within_2sigma': 0.8333333730697632, 'coverage_within_3sigma': 0.98333340883255}\n" - ] - } - ], + "outputs": [], "source": [ "print(extra2)" ] @@ -1713,7 +1117,7 @@ ], "metadata": { "kernelspec": { - "display_name": "QEC2", + "display_name": "qec", "language": "python", "name": "python3" }, @@ -1727,7 +1131,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.16" + "version": "3.10.19" } }, "nbformat": 4, From 81d00c0d9a1deada8d5b6b16c8c1e048feb5cf12 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Thu, 19 Feb 2026 12:13:38 +0000 Subject: [PATCH 02/36] formatting --- BOplayground.py | 596 +++++++++------ EAplayground.py | 72 +- RSplayground.py | 132 ++-- bayesian_optimization/bo.py | 103 ++- .../chaincomplexembedding.py | 442 ++++++++---- bayesian_optimization/encoder.py | 381 +++++----- bayesian_optimization/gadget.py | 286 +++++--- bayesian_optimization/gp.py | 154 ++-- bayesian_optimization/graphembedding.py | 227 +++--- bayesian_optimization/kernels.py | 115 +-- bayesian_optimization/objective_function.py | 108 ++- code_construction/code_construction.py | 677 +++++++++--------- dataset.py | 173 +++-- evaluation/circuit_level_noise.py | 605 ++++++++++------ evaluation/css_decode_sim.py | 7 +- evaluation/decoder_based_evaluation.py | 474 ++++++------ evolutionary_algorithm/ea.py | 41 +- initial_points.py | 98 +-- readme.ipynb | 210 ++++-- readme2.ipynb | 233 ++++-- test_bo.ipynb | 589 +++++++++------ test_gp.ipynb | 499 +++++++------ 22 files changed, 3758 insertions(+), 2464 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index cd9ea84..fdb07a4 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -1,7 +1,7 @@ # Todo: # add more BO experiments; -device = 'cuda' -DEVICE = 'cuda' +device = "cuda" +DEVICE = "cuda" import random import numpy as np import torch @@ -24,13 +24,17 @@ class HillClimbing: - def __init__(self, next_points_num, gnp, acquisition, device="cuda", validator=None): + def __init__( + self, next_points_num, gnp, acquisition, device="cuda", validator=None + ): self.next_points_num = int(next_points_num) self.gnp = gnp self.acquisition = acquisition self.device = device - self.validator = validator # 例如 lambda z: code_constructor.construct(z).k != 0 + self.validator = ( + validator # 例如 lambda z: code_constructor.construct(z).k != 0 + ) def hill_climbing_neighbors(self, x: torch.Tensor) -> torch.Tensor: @@ -48,7 +52,7 @@ def hill_climbing_neighbors(self, x: torch.Tensor) -> torch.Tensor: def __call__(self, gp) -> torch.Tensor: # 起点:np -> torch.float32 on device - X0_np = self.gnp(self.next_points_num) # [n, d], 0/1 + X0_np = self.gnp(self.next_points_num) # [n, d], 0/1 X0 = torch.tensor(X0_np, dtype=torch.float32, device=self.device) best_list = [] @@ -73,22 +77,23 @@ def __call__(self, gp) -> torch.Tensor: cand = torch.stack(best_list, dim=0) # [n, d], float32, 0/1 - if self.validator is not None: cand_np = cand.detach().cpu().numpy().astype(np.int64) for i in range(cand_np.shape[0]): if not self.validator(cand_np[i]): - tries = 0 while not self.validator(cand_np[i]): tries += 1 - cand_np[i] = np.random.randint(0, 2, cand_np[i].shape, dtype=np.int64) + cand_np[i] = np.random.randint( + 0, 2, cand_np[i].shape, dtype=np.int64 + ) if tries > 1000: - break + break cand = torch.tensor(cand_np, dtype=torch.float32, device=self.device) return cand # [n, d], float32(0/1), on device + class EIAcquisitionFunction: """ Expected Improvement (EI) acquisition function for Bayesian Optimization. @@ -120,21 +125,37 @@ class EIAcquisitionFunction: Epsilon used in log(pl + eps) transform. """ - def __init__(self, pl_to_obj_fn, best_value=None, jitter=1e-2, eps=1e-9, - device="cpu", normalizer=None, prob_lower=1e-12, prob_upper=1.0 - 1e-12, - log_eps=1e-8): + def __init__( + self, + pl_to_obj_fn, + best_value=None, + jitter=1e-2, + eps=1e-9, + device="cpu", + normalizer=None, + prob_lower=1e-12, + prob_upper=1.0 - 1e-12, + log_eps=1e-8, + ): self.pl_to_obj_fn = pl_to_obj_fn - self.best_value = best_value - self.jitter = float(jitter) - self.eps = float(eps) - self.device = device - self.prob_lower = float(prob_lower) - self.prob_upper = float(prob_upper) - self.log_eps = float(log_eps) + self.best_value = best_value + self.jitter = float(jitter) + self.eps = float(eps) + self.device = device + self.prob_lower = float(prob_lower) + self.prob_upper = float(prob_upper) + self.log_eps = float(log_eps) # EI maintains its own normalizer; create one if not provided. - self.normalizer = normalizer if normalizer is not None else LogStdNormalizer( - eps=self.log_eps, device=self.device, min_prob=self.prob_lower, max_prob=self.prob_upper + self.normalizer = ( + normalizer + if normalizer is not None + else LogStdNormalizer( + eps=self.log_eps, + device=self.device, + min_prob=self.prob_lower, + max_prob=self.prob_upper, + ) ) # ---------- Interface exposed to BO loop ---------- @@ -163,9 +184,11 @@ def _take_mean_std(self, ret): return ret[0], ret[1] if isinstance(ret, dict): mu = ret.get("mean", ret.get("mu")) - sd = ret.get("std", ret.get("sigma")) + sd = ret.get("std", ret.get("sigma")) if mu is None or sd is None: - raise RuntimeError("pl_to_obj_fn dict must contain mean/std (or mu/sigma).") + raise RuntimeError( + "pl_to_obj_fn dict must contain mean/std (or mu/sigma)." + ) return mu, sd raise RuntimeError(f"Unsupported return type from pl_to_obj_fn: {type(ret)}") @@ -182,7 +205,9 @@ def __call__(self, X, gp: torch.nn.Module): EI(x) = (μ_f - f* - ξ) Φ(Z) + σ_f φ(Z), where Z = (μ_f - f* - ξ) / σ_f, Φ = CDF, φ = PDF of standard normal. """ - assert self.best_value is not None, "best_value not set; call set_best_value() first." + assert self.best_value is not None, ( + "best_value not set; call set_best_value() first." + ) # Ensure X is a proper tensor on the correct device if not torch.is_tensor(X): @@ -198,11 +223,11 @@ def __call__(self, X, gp: torch.nn.Module): # Posterior in latent z-domain with gpytorch.settings.fast_pred_var(): - if hasattr(gp, "posterior"): # BoTorch-style API + if hasattr(gp, "posterior"): # BoTorch-style API post = gp.posterior(X) mu_lat = post.mean.reshape(-1) var_lat = post.variance.reshape(-1) - else: # Pure GPyTorch model + else: # Pure GPyTorch model mvn = gp(X) mu_lat = mvn.mean.reshape(-1) var_lat = mvn.variance.reshape(-1) @@ -211,7 +236,9 @@ def __call__(self, X, gp: torch.nn.Module): # Convert (μ_z, σ_z) → probability domain if not self.normalizer.is_fitted(): - raise RuntimeError("EI normalizer is not fitted. Call update_normalizer(pl_train) first.") + raise RuntimeError( + "EI normalizer is not fitted. Call update_normalizer(pl_train) first." + ) mu_pl, std_pl = self.normalizer.inverse_mean_std(mu_lat, std_lat) # Clamp probability and std for stability @@ -224,8 +251,16 @@ def __call__(self, X, gp: torch.nn.Module): for i in range(mu_pl.numel()): out = self.pl_to_obj_fn(X[i], mu_pl[i], std_pl[i]) mu_i, sd_i = self._take_mean_std(out) - mu_obj[i] = mu_i if torch.is_tensor(mu_i) else torch.tensor(mu_i, device=self.device, dtype=torch.float32) - std_obj[i] = sd_i if torch.is_tensor(sd_i) else torch.tensor(sd_i, device=self.device, dtype=torch.float32) + mu_obj[i] = ( + mu_i + if torch.is_tensor(mu_i) + else torch.tensor(mu_i, device=self.device, dtype=torch.float32) + ) + std_obj[i] = ( + sd_i + if torch.is_tensor(sd_i) + else torch.tensor(sd_i, device=self.device, dtype=torch.float32) + ) std_obj = std_obj.clamp_min(self.eps) @@ -236,15 +271,17 @@ def __call__(self, X, gp: torch.nn.Module): ei = imp * normal.cdf(Z) + std_obj * torch.exp(normal.log_prob(Z)) return ei + TensorLike = Union[torch.Tensor, np.ndarray, float] @dataclass class LogStdStats: """Stores the fitted statistics (mean, std, eps) for log-domain normalization.""" - mean: float # mean of log(pl + eps) + + mean: float # mean of log(pl + eps) scale: float # std of log(pl + eps) - eps: float # epsilon used in log(pl + eps) + eps: float # epsilon used in log(pl + eps) class LogStdNormalizer: @@ -255,7 +292,13 @@ class LogStdNormalizer: """ - def __init__(self, eps: float = 1e-8, min_prob: float = 1e-12, max_prob: float = 1.0 - 1e-12, device: str = "cpu"): + def __init__( + self, + eps: float = 1e-8, + min_prob: float = 1e-12, + max_prob: float = 1.0 - 1e-12, + device: str = "cpu", + ): self.stats: LogStdStats = LogStdStats(mean=0.0, scale=1.0, eps=float(eps)) self._fitted: bool = False self.min_prob = float(min_prob) @@ -284,7 +327,7 @@ def fit(self, pl: TensorLike) -> "LogStdNormalizer": pl_t = self._clamp_prob(pl_t) ylog = torch.log(pl_t + self.stats.eps) mean = ylog.mean().item() - std = ylog.std(unbiased=False).item() # population standard deviation + std = ylog.std(unbiased=False).item() # population standard deviation # Avoid degenerate scaling (very small std can cause instability) if std < 1e-12: @@ -322,7 +365,9 @@ def inverse_transform(self, z: TensorLike) -> torch.Tensor: # -------------------- Posterior inverse transform (used in EI acquisition) -------------------- @torch.no_grad() - def inverse_mean_std(self, mu_z: TensorLike, std_z: TensorLike) -> Tuple[torch.Tensor, torch.Tensor]: + def inverse_mean_std( + self, mu_z: TensorLike, std_z: TensorLike + ) -> Tuple[torch.Tensor, torch.Tensor]: """ If z ~ N(mu_z, std_z^2), then log(pl + eps) ~ N(mu, std^2), where: mu = mu_z * scale + mean @@ -336,19 +381,19 @@ def inverse_mean_std(self, mu_z: TensorLike, std_z: TensorLike) -> Tuple[torch.T (mean_pl, std_pl): expected mean and std of pl in probability domain. """ assert self._fitted, "Call fit(pl_train) before inverse_mean_std." - mu_z_t = self._to_tensor(mu_z, dtype=torch.float32) + mu_z_t = self._to_tensor(mu_z, dtype=torch.float32) std_z_t = self._to_tensor(std_z, dtype=torch.float32).abs() - mu = mu_z_t * self.stats.scale + self.stats.mean + mu = mu_z_t * self.stats.scale + self.stats.mean std = std_z_t * abs(self.stats.scale) # E[pl + eps] and Var(pl + eps) exp_half_var = torch.exp(0.5 * std**2) mean_pl_plus = torch.exp(mu) * exp_half_var - var_pl_plus = (torch.exp(std**2) - 1.0) * torch.exp(2.0 * mu + std**2) + var_pl_plus = (torch.exp(std**2) - 1.0) * torch.exp(2.0 * mu + std**2) mean_pl = mean_pl_plus - self.stats.eps - std_pl = var_pl_plus.clamp_min(1e-30).sqrt() + std_pl = var_pl_plus.clamp_min(1e-30).sqrt() # Clamp mean within valid probability range mean_pl = self._clamp_prob(mean_pl) @@ -374,18 +419,21 @@ def state_dict(self) -> Dict[str, Any]: "fitted": self._fitted, "min_prob": self.min_prob, "max_prob": self.max_prob, - "device": self.device + "device": self.device, } def load_state_dict(self, state: Dict[str, Any]): """Restore normalizer state from a dict.""" s = state["stats"] - self.stats = LogStdStats(mean=float(s["mean"]), scale=float(s["scale"]), eps=float(s["eps"])) + self.stats = LogStdStats( + mean=float(s["mean"]), scale=float(s["scale"]), eps=float(s["eps"]) + ) self._fitted = bool(state.get("fitted", True)) self.min_prob = float(state.get("min_prob", self.min_prob)) self.max_prob = float(state.get("max_prob", self.max_prob)) self.device = state.get("device", self.device) + class GPTrainer: """ Trainer for an ExactGP with a fixed structure (e.g., ScaleKernel ∘ Matern). @@ -402,28 +450,26 @@ def __init__( self, model: gpytorch.models.ExactGP, device: str = "cpu", - training_iter: int = 80, - lr: Optional[Dict[str, float]] = None, # {'embed':4e-4,'mean':1e-3,'kernel':1e-3,'like':2e-2} + lr: Optional[ + Dict[str, float] + ] = None, # {'embed':4e-4,'mean':1e-3,'kernel':1e-3,'like':2e-2} weight_decay: float = 1e-4, max_grad_norm: float = 2.0, optimizer_type: str = "adamw", recreate_optimizer_each_round: bool = True, - - scheduler_cfg: Optional[Dict[str, Any]] = None, # {'factor':0.5,'patience':5,'min_lr':1e-6} - early_stopping: Optional[Dict[str, Any]] = None,# {'patience':10,'tol':1e-4} - + scheduler_cfg: Optional[ + Dict[str, Any] + ] = None, # {'factor':0.5,'patience':5,'min_lr':1e-6} + early_stopping: Optional[Dict[str, Any]] = None, # {'patience':10,'tol':1e-4} use_priors: bool = True, priors_cfg: Optional[Dict[str, Dict[str, float]]] = None, - noise_floor: float = 1e-3, # lower bound (in z-domain) for likelihood noise + noise_floor: float = 1e-3, # lower bound (in z-domain) for likelihood noise lengthscale_bounds: Optional[Tuple[float, float]] = None, - warm_start: bool = True, - rescale_on_scaler_change: bool = True, # alpha = old_std / new_std + rescale_on_scaler_change: bool = True, # alpha = old_std / new_std carry_optimizer_state: bool = False, - - freeze_cfg: Optional[Dict[str, Any]] = None, # {'lengthscale_until_round': 2} - + freeze_cfg: Optional[Dict[str, Any]] = None, # {'lengthscale_until_round': 2} verbose: bool = True, log_every: Optional[int] = None, save_best_state: bool = True, @@ -437,14 +483,18 @@ def __init__( self.optimizer_type = optimizer_type.lower() self.recreate_optimizer_each_round = bool(recreate_optimizer_each_round) - self.scheduler_cfg = scheduler_cfg or {'factor': 0.5, 'patience': 5, 'min_lr': 1e-6} - self.early_stopping = early_stopping or {'patience': 10, 'tol': 1e-4} + self.scheduler_cfg = scheduler_cfg or { + "factor": 0.5, + "patience": 5, + "min_lr": 1e-6, + } + self.early_stopping = early_stopping or {"patience": 10, "tol": 1e-4} self.use_priors = bool(use_priors) self.priors_cfg = priors_cfg or { - 'lengthscale': {'type': 'lognormal', 'loc': 0.0, 'scale': 0.5}, - 'outputscale': {'type': 'lognormal', 'loc': 0.0, 'scale': 0.5}, - 'noise': {'type': 'lognormal', 'loc': -4.0, 'scale': 0.5}, + "lengthscale": {"type": "lognormal", "loc": 0.0, "scale": 0.5}, + "outputscale": {"type": "lognormal", "loc": 0.0, "scale": 0.5}, + "noise": {"type": "lognormal", "loc": -4.0, "scale": 0.5}, } self.noise_floor = float(noise_floor) self.lengthscale_bounds = lengthscale_bounds @@ -453,12 +503,12 @@ def __init__( self.rescale_on_scaler_change = bool(rescale_on_scaler_change) self.carry_optimizer_state = bool(carry_optimizer_state) - self.freeze_cfg = freeze_cfg or {'lengthscale_until_round': 0} + self.freeze_cfg = freeze_cfg or {"lengthscale_until_round": 0} self.verbose = bool(verbose) self.log_every = log_every self.save_best_state = bool(save_best_state) - self.lr = lr or {'embed': 4e-4, 'mean': 1e-3, 'kernel': 1e-3, 'like': 2e-2} + self.lr = lr or {"embed": 4e-4, "mean": 1e-3, "kernel": 1e-3, "like": 2e-2} # ---- State ---- self._optimizer: Optional[torch.optim.Optimizer] = None @@ -496,15 +546,17 @@ def on_normalizer_change(self, old_std: Optional[float], new_std: Optional[float return alpha = float(old_std / new_std) with torch.no_grad(): - if hasattr(self.model, "covar_module") and hasattr(self.model.covar_module, "outputscale"): - self.model.covar_module.outputscale.mul_(alpha ** 2) + if hasattr(self.model, "covar_module") and hasattr( + self.model.covar_module, "outputscale" + ): + self.model.covar_module.outputscale.mul_(alpha**2) try: - self.model.likelihood.noise.mul_(alpha ** 2) + self.model.likelihood.noise.mul_(alpha**2) except Exception: # Fallback: attempt to read/set noise through likelihood noise_covar try: noise = self._get_noise_value() - self._set_noise_value(noise * (alpha ** 2)) + self._set_noise_value(noise * (alpha**2)) except Exception: pass self._last_scaler_std = new_std @@ -514,9 +566,11 @@ def maybe_freeze_unfreeze(self, round_idx: int): Optionally freeze base kernel lengthscale parameters for early rounds. """ self._round_idx = int(round_idx) - until = int(self.freeze_cfg.get('lengthscale_until_round', 0)) - freeze = (round_idx <= until) - base_kernel = getattr(getattr(self.model, "covar_module", None), "base_kernel", None) + until = int(self.freeze_cfg.get("lengthscale_until_round", 0)) + freeze = round_idx <= until + base_kernel = getattr( + getattr(self.model, "covar_module", None), "base_kernel", None + ) if base_kernel is not None: for p in base_kernel.parameters(): p.requires_grad_(not freeze) @@ -531,9 +585,14 @@ def train_one_round(self) -> Dict[str, Any]: - best-state checkpointing (optional). """ model = self.model - model.train(); model.likelihood.train() - - if self._optimizer is None or self.recreate_optimizer_each_round or not self.carry_optimizer_state: + model.train() + model.likelihood.train() + + if ( + self._optimizer is None + or self.recreate_optimizer_each_round + or not self.carry_optimizer_state + ): self._optimizer = self._build_optimizer() self._scheduler = self._build_scheduler(self._optimizer) elif self._scheduler is None: @@ -544,8 +603,8 @@ def train_one_round(self) -> Dict[str, Any]: best_loss = float("inf") best_state = None no_improve = 0 - patience = int(self.early_stopping.get('patience', 10)) - tol = float(self.early_stopping.get('tol', 1e-4)) + patience = int(self.early_stopping.get("patience", 10)) + tol = float(self.early_stopping.get("tol", 1e-4)) self._history = [] train_x, train_y = model.train_inputs[0], model.train_targets @@ -573,21 +632,29 @@ def train_one_round(self) -> Dict[str, Any]: if self.save_best_state: best_state = { "model": copy.deepcopy(model.state_dict()), - "likelihood": copy.deepcopy(model.likelihood.state_dict()) + "likelihood": copy.deepcopy(model.likelihood.state_dict()), } else: no_improve += 1 - if self.verbose and (self.log_every is None or it % max(1, self.log_every) == 0 or it == self.training_iter): + if self.verbose and ( + self.log_every is None + or it % max(1, self.log_every) == 0 + or it == self.training_iter + ): ls_val = self._safe_get_lengthscale() - out_v = self._safe_get_outputscale() - nz_v = self._safe_get_noise() - print(f"[round {self._round_idx:>3d} | {it:>4d}/{self.training_iter}] " - f"nll={cur:.4f} len={ls_val} out={out_v:.3e} noise={nz_v:.3e}") + out_v = self._safe_get_outputscale() + nz_v = self._safe_get_noise() + print( + f"[round {self._round_idx:>3d} | {it:>4d}/{self.training_iter}] " + f"nll={cur:.4f} len={ls_val} out={out_v:.3e} noise={nz_v:.3e}" + ) if no_improve >= patience and rel_impr < tol: if self.verbose: - print(f"[round {self._round_idx:>3d}] early stop @ {it}, best nll={best_loss:.4f}") + print( + f"[round {self._round_idx:>3d}] early stop @ {it}, best nll={best_loss:.4f}" + ) break if self.save_best_state and best_state is not None: @@ -609,18 +676,40 @@ def train_one_round(self) -> Dict[str, Any]: # ================= Internal Utilities ================= def _build_optimizer(self) -> torch.optim.Optimizer: # Group parameters by module for separate learning rates. - params_embed = list(getattr(self.model, "embed", torch.nn.Module()).parameters()) - params_kernel = list(getattr(self.model, "covar_module", torch.nn.Module()).parameters()) - params_like = list(self.model.likelihood.parameters()) - params_mean = list(getattr(self.model, "mean_module", torch.nn.Module()).parameters()) + params_embed = list( + getattr(self.model, "embed", torch.nn.Module()).parameters() + ) + params_kernel = list( + getattr(self.model, "covar_module", torch.nn.Module()).parameters() + ) + params_like = list(self.model.likelihood.parameters()) + params_mean = list( + getattr(self.model, "mean_module", torch.nn.Module()).parameters() + ) lr = self.lr # learning rate groups = [ - {'params': params_embed, 'lr': lr.get('embed', 4e-4), 'weight_decay': self.weight_decay}, - {'params': params_mean, 'lr': lr.get('mean', 1e-3), 'weight_decay': self.weight_decay}, - {'params': params_kernel, 'lr': lr.get('kernel', 1e-3), 'weight_decay': self.weight_decay}, - {'params': params_like, 'lr': lr.get('like', 2e-2), 'weight_decay': self.weight_decay/5}, + { + "params": params_embed, + "lr": lr.get("embed", 4e-4), + "weight_decay": self.weight_decay, + }, + { + "params": params_mean, + "lr": lr.get("mean", 1e-3), + "weight_decay": self.weight_decay, + }, + { + "params": params_kernel, + "lr": lr.get("kernel", 1e-3), + "weight_decay": self.weight_decay, + }, + { + "params": params_like, + "lr": lr.get("like", 2e-2), + "weight_decay": self.weight_decay / 5, + }, ] if self.optimizer_type == "adam": return torch.optim.Adam(groups) @@ -629,35 +718,43 @@ def _build_optimizer(self) -> torch.optim.Optimizer: def _build_scheduler(self, optimizer) -> torch.optim.lr_scheduler.ReduceLROnPlateau: cfg = self.scheduler_cfg return torch.optim.lr_scheduler.ReduceLROnPlateau( - optimizer, mode='min', - factor=float(cfg.get('factor', 0.5)), - patience=int(cfg.get('patience', 5)), - min_lr=float(cfg.get('min_lr', 1e-6)) + optimizer, + mode="min", + factor=float(cfg.get("factor", 0.5)), + patience=int(cfg.get("patience", 5)), + min_lr=float(cfg.get("min_lr", 1e-6)), ) def _register_priors_safely(self): """Attach priors to the **constrained** parameter names (lengthscale/outputscale/noise).""" from gpytorch.priors import LogNormalPrior + # lengthscale prior try: bk = self.model.covar_module.base_kernel - loc = float(self.priors_cfg['lengthscale']['loc']) - scale = float(self.priors_cfg['lengthscale']['scale']) - bk.register_prior("lengthscale_prior", LogNormalPrior(loc, scale), "lengthscale") + loc = float(self.priors_cfg["lengthscale"]["loc"]) + scale = float(self.priors_cfg["lengthscale"]["scale"]) + bk.register_prior( + "lengthscale_prior", LogNormalPrior(loc, scale), "lengthscale" + ) except Exception: pass # outputscale prior try: - loc = float(self.priors_cfg['outputscale']['loc']) - scale = float(self.priors_cfg['outputscale']['scale']) - self.model.covar_module.register_prior("outputscale_prior", LogNormalPrior(loc, scale), "outputscale") + loc = float(self.priors_cfg["outputscale"]["loc"]) + scale = float(self.priors_cfg["outputscale"]["scale"]) + self.model.covar_module.register_prior( + "outputscale_prior", LogNormalPrior(loc, scale), "outputscale" + ) except Exception: pass # noise prior try: - loc = float(self.priors_cfg['noise']['loc']) - scale = float(self.priors_cfg['noise']['scale']) - self.model.likelihood.register_prior("noise_prior", LogNormalPrior(loc, scale), "noise") + loc = float(self.priors_cfg["noise"]["loc"]) + scale = float(self.priors_cfg["noise"]["scale"]) + self.model.likelihood.register_prior( + "noise_prior", LogNormalPrior(loc, scale), "noise" + ) except Exception: pass @@ -665,7 +762,10 @@ def _register_constraints_safely(self): # Noise lower bound (constraint on raw_noise) try: self.model.likelihood.noise_covar.register_constraint( - "raw_noise", gpytorch.constraints.GreaterThan(torch.tensor(self.noise_floor, device=self.device)) + "raw_noise", + gpytorch.constraints.GreaterThan( + torch.tensor(self.noise_floor, device=self.device) + ), ) except Exception: pass @@ -698,7 +798,9 @@ def _get_noise_value(self) -> float: return float(self.model.likelihood.noise.detach().cpu().item()) except Exception: try: - return float(self.model.likelihood.noise_covar.noise.detach().cpu().item()) + return float( + self.model.likelihood.noise_covar.noise.detach().cpu().item() + ) except Exception: return float("nan") @@ -708,46 +810,66 @@ def _set_noise_value(self, v: float): self.model.likelihood.noise.copy_(torch.tensor(v, device=self.device)) except Exception: try: - self.model.likelihood.noise_covar.noise.copy_(torch.tensor(v, device=self.device)) + self.model.likelihood.noise_covar.noise.copy_( + torch.tensor(v, device=self.device) + ) except Exception: pass def _safe_get_noise(self) -> float: return self._get_noise_value() -class E(): - def __init__(self,code_constructor,views_info ): + +class E: + def __init__(self, code_constructor, views_info): self.code_constructor = code_constructor - self.encoder = CSSEncoder(views_info,mode ='relations') + self.encoder = CSSEncoder(views_info, mode="relations") + + def encode_single(self, x): + return self.encoder.encode( + self.code_constructor.construct(np.array(x).astype(int)) + ) - def encode_single(self,x): - return self.encoder.encode(self.code_constructor.construct(np.array(x).astype(int))) - def encode(self,x): + def encode(self, x): # x: B views return [self.encode_single(i) for i in x] + + views_info = [ - {"name":"decode", - "partite_classes":["SZ","DQ","SX"], - "relations":["SZ_DQ","DQ_SZ","DQ_SX","SX_DQ"], - "weight_mode":"count", "log1p":True}, - {"name":"xlogic", - "partite_classes":["DQ","SX","LX"], - "relations":["DQ_SX","SX_DQ","LX_DQ","DQ_LX"], - "weight_mode":"count", "log1p":True}, - {"name":"zlogic", - "partite_classes":["SZ","DQ","LZ"], - "relations":["SZ_DQ","DQ_SZ","LZ_DQ","DQ_LZ"], - "weight_mode":"count", "log1p":True}, + { + "name": "decode", + "partite_classes": ["SZ", "DQ", "SX"], + "relations": ["SZ_DQ", "DQ_SZ", "DQ_SX", "SX_DQ"], + "weight_mode": "count", + "log1p": True, + }, + { + "name": "xlogic", + "partite_classes": ["DQ", "SX", "LX"], + "relations": ["DQ_SX", "SX_DQ", "LX_DQ", "DQ_LX"], + "weight_mode": "count", + "log1p": True, + }, + { + "name": "zlogic", + "partite_classes": ["SZ", "DQ", "LZ"], + "relations": ["SZ_DQ", "DQ_SZ", "LZ_DQ", "DQ_LZ"], + "weight_mode": "count", + "log1p": True, + }, ] -def get_model_(X, y, kernel_type='ard_rbf', mean_type='linear', mean_input=64,embed_dim=128): - encoder = E(code_constructor,views_info) +def get_model_( + X, y, kernel_type="ard_rbf", mean_type="linear", mean_input=64, embed_dim=128 +): + + encoder = E(code_constructor, views_info) # NN embedder embedding = ChainComplexEmbedder( views_info=views_info, - d_model=embed_dim , + d_model=embed_dim, num_layers=4, view_aggr="sum", num_bases=4, @@ -757,19 +879,18 @@ def get_model_(X, y, kernel_type='ard_rbf', mean_type='linear', mean_input=64,em dropout=0.1, ).to(device) - - # kernel - if kernel_type == 'ard_rbf': + if kernel_type == "ard_rbf": base = RBFKernel(ard_num_dims=embed_dim) kernel = ScaleKernel(base) - elif kernel_type == 'matern': + elif kernel_type == "matern": base = MaternKernel(nu=1.5, ard_num_dims=embed_dim) kernel = ScaleKernel(base) - elif kernel_type == 'spectral_mixture': + elif kernel_type == "spectral_mixture": kernel = SpectralMixtureKernel(num_mixtures=4, ard_num_dims=embed_dim) - elif kernel_type == 'rbf_plus_periodic': + elif kernel_type == "rbf_plus_periodic": from gpytorch.kernels import PeriodicKernel + rbf = ScaleKernel(RBFKernel(ard_num_dims=embed_dim)) periodic = ScaleKernel(PeriodicKernel(ard_num_dims=embed_dim)) kernel = rbf + periodic @@ -784,7 +905,8 @@ def get_model_(X, y, kernel_type='ard_rbf', mean_type='linear', mean_input=64,em train_y = y.to(device).float().view(-1) gp = GaussianProcess_QEC( - train_x, train_y, + train_x, + train_y, likelihood=likelihood, kernel=kernel, encoder=encoder.encode, @@ -794,67 +916,77 @@ def get_model_(X, y, kernel_type='ard_rbf', mean_type='linear', mean_input=64,em ).to(device) return gp + + def set_all_seeds(seed: int = 42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed(seed) - torch.cuda.manual_seed_all(seed) + torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False -class Get_new_points_function(): - def __init__(self,method='qc-ldpc-hgp',code_constructor = None,encode='None'): + + +class Get_new_points_function: + def __init__(self, method="qc-ldpc-hgp", code_constructor=None, encode="None"): self.method = method self.code_constructor = code_constructor self.encode = encode self.init = False - def get_new_points_function(self,number): - if self.method == 'qc-ldpc-hgp': + def get_new_points_function(self, number): + if self.method == "qc-ldpc-hgp": new_points = self.get_new_points_HGP(number) - elif self.method == 'bb': + elif self.method == "bb": new_points = self.get_new_bb_vector(number) return new_points - - def get_new_points_HGP(self,number): - return np.random.randint(0, self.hyperparameters['m'] + 1, (number, self.hyperparameters['p'] * self.hyperparameters['q'])) - def get_new_bb_vector(self,number): + def get_new_points_HGP(self, number): + return np.random.randint( + 0, + self.hyperparameters["m"] + 1, + (number, self.hyperparameters["p"] * self.hyperparameters["q"]), + ) + + def get_new_bb_vector(self, number): results = [] - l = self.code_constructor.para_dict['l'] - g = self.code_constructor.para_dict['g'] - if self.init == False and l==12 and g==999: - print('best known bb code added to initial points') - self.init=True - - a = np.zeros((l+g-1)*2) - a[3]=1 - a[11+1]=1 - a[11+2]=1 - a[17+1]=1 - a[17+2]=1 - a[17+11+3]=1 + l = self.code_constructor.para_dict["l"] + g = self.code_constructor.para_dict["g"] + if self.init == False and l == 12 and g == 999: + print("best known bb code added to initial points") + self.init = True + + a = np.zeros((l + g - 1) * 2) + a[3] = 1 + a[11 + 1] = 1 + a[11 + 2] = 1 + a[17 + 1] = 1 + a[17 + 2] = 1 + a[17 + 11 + 3] = 1 results.append(a) - while number>0: - new_point = np.random.randint(0,2, size=(l+g-1)*2) + while number > 0: + new_point = np.random.randint(0, 2, size=(l + g - 1) * 2) c = self.code_constructor.construct(new_point) - if c.k==0: + if c.k == 0: continue else: results.append(new_point) number -= 1 return np.array(results) -if __name__ == '__main__': + + +if __name__ == "__main__": import pickle import sys - + if len(sys.argv) == 3: - seed= int(sys.argv[1]) + seed = int(sys.argv[1]) dataset_index = int(sys.argv[2]) lambda_ = 1 - elif len(sys.argv) >3: - seed= int(sys.argv[1]) + elif len(sys.argv) > 3: + seed = int(sys.argv[1]) dataset_index = int(sys.argv[2]) lambda_ = float(sys.argv[3]) else: @@ -864,22 +996,26 @@ def get_new_bb_vector(self,number): set_all_seeds(seed) l = 12 - g = 6 # g here is m in Bravyi et al's paper - print(f'(l,g)=({l},{g}), dataset_index = {dataset_index}, seed={seed}, lambda = {lambda_}') - - - para_dict = {'l':l,'g':g} - code_class = 'bb' - - - code_constructor = CodeConstructor(method=code_class,para_dict = para_dict) + g = 6 # g here is m in Bravyi et al's paper + print( + f"(l,g)=({l},{g}), dataset_index = {dataset_index}, seed={seed}, lambda = {lambda_}" + ) + + para_dict = {"l": l, "g": g} + code_class = "bb" + + code_constructor = CodeConstructor(method=code_class, para_dict=para_dict) # define objective function - pp=0.05 - Obj_Func = ObjectiveFunction(code_constructor,lambda_ = lambda_, pp=pp,decoder_param={'trail':10_000}) + pp = 0.05 + Obj_Func = ObjectiveFunction( + code_constructor, lambda_=lambda_, pp=pp, decoder_param={"trail": 10_000} + ) obj_func = Obj_Func.forward pl_to_obj = Obj_Func.pl_to_obj_with_std # method of sampling new points - gnp = Get_new_points_function(method=code_class,code_constructor=code_constructor).get_new_points_function + gnp = Get_new_points_function( + method=code_class, code_constructor=code_constructor + ).get_new_points_function # initial points: # init_num = 20 # X_init = gnp(init_num) @@ -889,123 +1025,121 @@ def get_new_bb_vector(self,number): # y,pl = obj_func(x) # y_init.append(y) # pl_init.append(pl) - if l ==6 and g==3: + if l == 6 and g == 3: if lambda_ == 1: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_1.0_63.pkl" + init_data_file = ( + f"./data/BO_initial_points/BO_initial_points_{dataset_index}_1.0_63.pkl" + ) else: init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}_63.pkl" else: if lambda_ == 1: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_1.0.pkl" + init_data_file = ( + f"./data/BO_initial_points/BO_initial_points_{dataset_index}_1.0.pkl" + ) else: init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" # file with 63 suffix has (l,m)=(6,3). Otherwise (l,m)=(12,6) with open(init_data_file, "rb") as f: data = pickle.load(f) - X_init = data['X'] - y_init = data['y'] - pl_init = data['pl'] + X_init = data["X"] + y_init = data["y"] + pl_init = data["pl"] - X_init = torch.tensor(X_init,dtype=torch.float32) + X_init = torch.tensor(X_init, dtype=torch.float32) X_init.to(DEVICE) - y_init = torch.tensor(y_init,dtype=torch.float32) + y_init = torch.tensor(y_init, dtype=torch.float32) y_init.to(DEVICE) - pl_init = torch.tensor(pl_init,dtype=torch.float32) + pl_init = torch.tensor(pl_init, dtype=torch.float32) pl_init.to(DEVICE) # get gp model model = get_model_( X_init, pl_init, - kernel_type='matern', - mean_type='linear', + kernel_type="matern", + mean_type="linear", mean_input=128, ) # gp trainer trainer = GPTrainer( model=model, device=DEVICE, - # --- Training and regularization --- training_iter=80, - lr={'embed': 4e-4, 'mean': 1e-3, 'kernel': 1e-3, 'like': 2e-2}, + lr={"embed": 4e-4, "mean": 1e-3, "kernel": 1e-3, "like": 2e-2}, weight_decay=1e-4, max_grad_norm=2.0, - optimizer_type='adamw', + optimizer_type="adamw", recreate_optimizer_each_round=True, - # --- Scheduler and early stopping --- - scheduler_cfg={'factor': 0.5, 'patience': 5, 'min_lr': 1e-6}, - early_stopping={'patience': 10, 'tol': 1e-4}, - + scheduler_cfg={"factor": 0.5, "patience": 5, "min_lr": 1e-6}, + early_stopping={"patience": 10, "tol": 1e-4}, # --- Priors and constraints (helpful for small datasets) --- use_priors=True, - noise_floor=1e-3, # Lower bound for z-domain noise to avoid overfitting - lengthscale_bounds=None, # If X is normalized to [0,1], one may use (1e-2, 10.) - + noise_floor=1e-3, # Lower bound for z-domain noise to avoid overfitting + lengthscale_bounds=None, # If X is normalized to [0,1], one may use (1e-2, 10.) # --- Warm start and scaler rescaling --- warm_start=True, - rescale_on_scaler_change=True, # Recommended: True + rescale_on_scaler_change=True, # Recommended: True carry_optimizer_state=False, - # --- Freeze base kernel lengthscale in early rounds (stabilizes small-data regime) --- - freeze_cfg={'lengthscale_until_round': 2}, - + freeze_cfg={"lengthscale_until_round": 2}, verbose=True, log_every=8, save_best_state=True, ) # acquisition function: acq = EIAcquisitionFunction( - pl_to_obj_fn = pl_to_obj, - best_value = None, - jitter = 1e-2, - eps = 1e-9, - device = DEVICE, - normalizer = None, - prob_lower = 1e-12, - prob_upper = 1.0 - 1e-12, - log_eps = 1e-8 + pl_to_obj_fn=pl_to_obj, + best_value=None, + jitter=1e-2, + eps=1e-9, + device=DEVICE, + normalizer=None, + prob_lower=1e-12, + prob_upper=1.0 - 1e-12, + log_eps=1e-8, ) + # hill climbing: def bb_validator(candidate_np): return code_constructor.construct(candidate_np).k != 0 + next_points_num = 4 hc = HillClimbing( - next_points_num = next_points_num, # the candidate number - gnp = gnp, # get new points function - acquisition = acq, - device = DEVICE, - validator = bb_validator + next_points_num=next_points_num, # the candidate number + gnp=gnp, # get new points function + acquisition=acq, + device=DEVICE, + validator=bb_validator, ) # assemble BO bo_iterations = 50 bo = BO_on_QEC( - gp = model, - gp_trainer = trainer, - acquisition_function = acq, - suggest_next = hc, - objective_function = obj_func, - initial_X = X_init, - initial_pl = pl_init, - initial_y = y_init, - BO_iterations = bo_iterations, - description = 'BB-BO (GP+EI+HC)', - device = DEVICE, - pretrain = True + gp=model, + gp_trainer=trainer, + acquisition_function=acq, + suggest_next=hc, + objective_function=obj_func, + initial_X=X_init, + initial_pl=pl_init, + initial_y=y_init, + BO_iterations=bo_iterations, + description="BB-BO (GP+EI+HC)", + device=DEVICE, + pretrain=True, ) - best_x,best_y,evaluation_history = bo.run() + best_x, best_y, evaluation_history = bo.run() # The best-so-far results of bo (including initial points) flat = [v for row in evaluation_history for v in row] y_init_list = [i.item() for i in y_init] flat = y_init_list + flat - with open(f'./data/BO_results/BO_{l}_{g}_{dataset_index}_{seed}_{lambda_}.pkl','wb') as f: - results = { - 'best_x':best_x, - 'best_y':best_y, - 'evaluation_history':flat - } + with open( + f"./data/BO_results/BO_{l}_{g}_{dataset_index}_{seed}_{lambda_}.pkl", "wb" + ) as f: + results = {"best_x": best_x, "best_y": best_y, "evaluation_history": flat} pickle.dump(results, f) diff --git a/EAplayground.py b/EAplayground.py index cb74258..2e68a26 100644 --- a/EAplayground.py +++ b/EAplayground.py @@ -18,11 +18,11 @@ if len(sys.argv) == 3: - seed= int(sys.argv[1]) - dataset_index = int(sys.argv[2]) - lambda_ = 1 -elif len(sys.argv) >3: - seed= int(sys.argv[1]) + seed = int(sys.argv[1]) + dataset_index = int(sys.argv[2]) + lambda_ = 1 +elif len(sys.argv) > 3: + seed = int(sys.argv[1]) dataset_index = int(sys.argv[2]) lambda_ = float(sys.argv[3]) else: @@ -30,9 +30,12 @@ dataset_index = 0 lambda_ = 1 -l=12 -g=6 -print(f'(l,g)=({l},{g}), dataset_index = {dataset_index}, seed={seed}, lambda = {lambda_}') +l = 12 +g = 6 +print( + f"(l,g)=({l},{g}), dataset_index = {dataset_index}, seed={seed}, lambda = {lambda_}" +) + class MySampling(Sampling): def __init__(self, init_samples): @@ -41,38 +44,47 @@ def __init__(self, init_samples): def _do(self, problem, n_samples, **kwargs): return self.init_samples -para_dict = {'l':l,'g':g} -code_class = 'bb' -code_constructor = CodeConstructor(method=code_class,para_dict = para_dict) +para_dict = {"l": l, "g": g} +code_class = "bb" + + +code_constructor = CodeConstructor(method=code_class, para_dict=para_dict) # define objective function -pp=0.05 -Obj_Func = ObjectiveFunction(code_constructor,lambda_=lambda_, pp=pp,decoder_param={'trail':10_000}) +pp = 0.05 +Obj_Func = ObjectiveFunction( + code_constructor, lambda_=lambda_, pp=pp, decoder_param={"trail": 10_000} +) obj_func = Obj_Func.forward -if l ==6 and g==3: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}_63.pkl" +if l == 6 and g == 3: + init_data_file = ( + f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}_63.pkl" + ) else: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" + init_data_file = ( + f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" + ) # file with 63 suffix has (l,m)=(6,3). Otherwise (l,m)=(12,6) with open(init_data_file, "rb") as f: data = pickle.load(f) - X_init = data['X'] - y_init = data['y'] - pl_init = data['pl'] -problem = BivariateBicycleCodeEvolutionaryOptimization(l=l,m=g,obj_func=Obj_Func) + X_init = data["X"] + y_init = data["y"] + pl_init = data["pl"] +problem = BivariateBicycleCodeEvolutionaryOptimization(l=l, m=g, obj_func=Obj_Func) algorithm = GA( pop_size=20, sampling=MySampling(X_init), crossover=UniformCrossover(prob=1.0), mutation=PM(prob=1.0, eta=3.0, vtype=float, repair=RoundingRepair()), - eliminate_duplicates=True + eliminate_duplicates=True, ) -res = minimize(problem, - algorithm, - termination=('n_gen', 11), - seed=seed, +res = minimize( + problem, + algorithm, + termination=("n_gen", 11), + seed=seed, ) flat2 = [-v for row in problem.evaluation_history for v in row] @@ -81,10 +93,12 @@ def _do(self, problem, n_samples, **kwargs): print(problem.best_parameters) print("Best solution found: \nX = %s\nF = %s" % (res.X, -res.F)) -with open(f'./data/BO_results/EA_{l}_{g}_{dataset_index}_{seed}_{lambda_}.pkl','wb') as f: +with open( + f"./data/BO_results/EA_{l}_{g}_{dataset_index}_{seed}_{lambda_}.pkl", "wb" +) as f: results = { - 'best_x':problem.best_parameters, - 'best_y':problem.best_result, - 'evaluation_history':flat2, + "best_x": problem.best_parameters, + "best_y": problem.best_result, + "evaluation_history": flat2, } pickle.dump(results, f) diff --git a/RSplayground.py b/RSplayground.py index 0aada6e..6041211 100644 --- a/RSplayground.py +++ b/RSplayground.py @@ -11,63 +11,69 @@ def set_all_seeds(seed: int = 42): np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed(seed) - torch.cuda.manual_seed_all(seed) + torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False -class Get_new_points_function(): - def __init__(self,method='qc-ldpc-hgp',code_constructor = None,encode='None'): + + +class Get_new_points_function: + def __init__(self, method="qc-ldpc-hgp", code_constructor=None, encode="None"): self.method = method self.code_constructor = code_constructor self.encode = encode self.init = False - def get_new_points_function(self,number): - if self.method == 'qc-ldpc-hgp': + def get_new_points_function(self, number): + if self.method == "qc-ldpc-hgp": new_points = self.get_new_points_HGP(number) - elif self.method == 'bb': + elif self.method == "bb": new_points = self.get_new_bb_vector(number) return new_points - - def get_new_points_HGP(self,number): - return np.random.randint(0, code_constructor.para_dict['m'] + 1, (number, code_constructor.para_dict['p'] * code_constructor.para_dict['q'])) - def get_new_bb_vector(self,number): + def get_new_points_HGP(self, number): + return np.random.randint( + 0, + code_constructor.para_dict["m"] + 1, + (number, code_constructor.para_dict["p"] * code_constructor.para_dict["q"]), + ) + + def get_new_bb_vector(self, number): results = [] - l = self.code_constructor.para_dict['l'] - g = self.code_constructor.para_dict['g'] - if self.init == False and l==12 and g==999: - print('best known bb code added to initial points') - self.init=True - - a = np.zeros((l+g-1)*2) - a[3]=1 - a[11+1]=1 - a[11+2]=1 - a[17+1]=1 - a[17+2]=1 - a[17+11+3]=1 + l = self.code_constructor.para_dict["l"] + g = self.code_constructor.para_dict["g"] + if self.init == False and l == 12 and g == 999: + print("best known bb code added to initial points") + self.init = True + + a = np.zeros((l + g - 1) * 2) + a[3] = 1 + a[11 + 1] = 1 + a[11 + 2] = 1 + a[17 + 1] = 1 + a[17 + 2] = 1 + a[17 + 11 + 3] = 1 results.append(a) - while number>0: - new_point = np.random.randint(0,2, size=(l+g-1)*2) + while number > 0: + new_point = np.random.randint(0, 2, size=(l + g - 1) * 2) c = self.code_constructor.construct(new_point) - if c.k==0: + if c.k == 0: continue else: results.append(new_point) number -= 1 return np.array(results) - -if __name__ == '__main__': + + +if __name__ == "__main__": import pickle - - + if len(sys.argv) == 3: - seed= int(sys.argv[1]) + seed = int(sys.argv[1]) dataset_index = int(sys.argv[2]) lambda_ = 1 - elif len(sys.argv) >3: - seed= int(sys.argv[1]) + elif len(sys.argv) > 3: + seed = int(sys.argv[1]) dataset_index = int(sys.argv[2]) lambda_ = float(sys.argv[3]) else: @@ -76,54 +82,58 @@ def get_new_bb_vector(self,number): lambda_ = 1 set_all_seeds(seed) l = 12 - g = 6 # g here is m in Bravyi et al's paper - print(f'(l,g)=({l},{g}), dataset_index = {dataset_index}, seed={seed}, lambda = {lambda_}') - - para_dict = {'l':l,'g':g} - code_class = 'bb' - - if l ==6 and g==3: + g = 6 # g here is m in Bravyi et al's paper + print( + f"(l,g)=({l},{g}), dataset_index = {dataset_index}, seed={seed}, lambda = {lambda_}" + ) + + para_dict = {"l": l, "g": g} + code_class = "bb" + + if l == 6 and g == 3: init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}_63.pkl" else: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" + init_data_file = ( + f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" + ) # file with 63 suffix has (l,m)=(6,3). Otherwise (l,m)=(12,6) with open(init_data_file, "rb") as f: data = pickle.load(f) - X_init = data['X'] - y_init = data['y'] - pl_init = data['pl'] + X_init = data["X"] + y_init = data["y"] + pl_init = data["pl"] - - code_constructor = CodeConstructor(method=code_class,para_dict = para_dict) + code_constructor = CodeConstructor(method=code_class, para_dict=para_dict) # define objective function - pp=0.05 - Obj_Func = ObjectiveFunction(code_constructor,lambda_ = lambda_ ,pp=pp,decoder_param={'trail':10_000}) + pp = 0.05 + Obj_Func = ObjectiveFunction( + code_constructor, lambda_=lambda_, pp=pp, decoder_param={"trail": 10_000} + ) obj_func = Obj_Func.forward pl_to_obj = Obj_Func.pl_to_obj_with_std # method of sampling new points - gnp = Get_new_points_function(method=code_class,code_constructor=code_constructor).get_new_points_function - X_random = gnp(4*50) + gnp = Get_new_points_function( + method=code_class, code_constructor=code_constructor + ).get_new_points_function + X_random = gnp(4 * 50) flat3 = [] best_x = None best_y = -999 x_history = [] pl_history = [] - for i in range(4*50): - F,pL = obj_func(X_random[i]) - print(f'The {i}th point, obj_func:{F}') + for i in range(4 * 50): + F, pL = obj_func(X_random[i]) + print(f"The {i}th point, obj_func:{F}") flat3.append(F) x_history.append(X_random[i]) pl_history.append(pL) - if F>=best_y: + if F >= best_y: best_x = X_random[i] best_y = F - flat3 = y_init + flat3 - with open(f'./data/BO_results/RS_{l}_{g}_{dataset_index}_{seed}_{lambda_}.pkl','wb') as f: - results = { - 'best_x':best_x, - 'best_y':best_y, - 'evaluation_history':flat3 - } + with open( + f"./data/BO_results/RS_{l}_{g}_{dataset_index}_{seed}_{lambda_}.pkl", "wb" + ) as f: + results = {"best_x": best_x, "best_y": best_y, "evaluation_history": flat3} pickle.dump(results, f) diff --git a/bayesian_optimization/bo.py b/bayesian_optimization/bo.py index 27b60f4..7b02b99 100644 --- a/bayesian_optimization/bo.py +++ b/bayesian_optimization/bo.py @@ -78,19 +78,22 @@ class BO_on_QEC: If True, fit normalizer with initial pl, sync trainer, and perform one warmup training round before BO starts. """ - def __init__(self, - gp, - gp_trainer, # GPTrainer instance - acquisition_function, # EI with internal normalizer - suggest_next, - objective_function, # (x_single_np) -> (F, pL_total) - initial_X: torch.Tensor, - initial_pl: torch.Tensor, # probability space (pl) - initial_y: torch.Tensor, # objective space (F) - BO_iterations=10, - description='', - device="cpu", - pretrain=True): + + def __init__( + self, + gp, + gp_trainer, # GPTrainer instance + acquisition_function, # EI with internal normalizer + suggest_next, + objective_function, # (x_single_np) -> (F, pL_total) + initial_X: torch.Tensor, + initial_pl: torch.Tensor, # probability space (pl) + initial_y: torch.Tensor, # objective space (F) + BO_iterations=10, + description="", + device="cpu", + pretrain=True, + ): self.gp = gp self.trainer = gp_trainer @@ -99,16 +102,18 @@ def __init__(self, self.suggest_next = suggest_next self.device = device - self.X = initial_X.to(self.device, dtype=torch.float32) - self.pl = initial_pl.to(self.device, dtype=torch.float32) # probability space labels - self.y = initial_y.to(self.device, dtype=torch.float32) # objective values F + self.X = initial_X.to(self.device, dtype=torch.float32) + self.pl = initial_pl.to( + self.device, dtype=torch.float32 + ) # probability space labels + self.y = initial_y.to(self.device, dtype=torch.float32) # objective values F self.BO_iterations = int(BO_iterations) self.description = description self.best_value = self.y.max() self.best_parameters = self.X[self.y.argmax()].clone() - print(f'Initial best value: {self.best_value.item():.4f}') + print(f"Initial best value: {self.best_value.item():.4f}") # Online diagnostic log: per-iteration list of records (dicts) self.pred_vs_true_history = [] @@ -177,9 +182,11 @@ def run(self): pbar = tqdm.tqdm(range(self.BO_iterations), desc=self.description) # TSV for online diagnostics (pred vs. true); easy to inspect externally. - metrics_tsv = open('pred_vs_true.tsv', 'a', encoding='utf-8') + metrics_tsv = open("pred_vs_true.tsv", "a", encoding="utf-8") if metrics_tsv.tell() == 0: - metrics_tsv.write("round\tidx\tmu_pl\tstd_pl\tpl_true\tr_pl\tz_true\tmu_z\tstd_z\tr_z\tcover68_pl\tcover95_pl\n") + metrics_tsv.write( + "round\tidx\tmu_pl\tstd_pl\tpl_true\tr_pl\tz_true\tmu_z\tstd_z\tr_z\tcover68_pl\tcover95_pl\n" + ) for rnd in pbar: # --- Step 0: refresh EI's incumbent best objective --- @@ -197,20 +204,30 @@ def run(self): if not self.acquisition_function.normalizer.is_fitted(): # Should not happen in practice (pretraining/previous rounds fit it). self.acquisition_function.update_normalizer(self.pl) - mu_pl_pred, std_pl_pred = self.acquisition_function.normalizer.inverse_mean_std(mu_z_pred, std_z_pred) + mu_pl_pred, std_pl_pred = ( + self.acquisition_function.normalizer.inverse_mean_std( + mu_z_pred, std_z_pred + ) + ) mu_pl_pred = mu_pl_pred.clamp(1e-12, 1.0 - 1e-12) std_pl_pred = std_pl_pred.clamp_min(1e-12) # --- Step 2: single-sample evaluations (objective & true pl) --- y_list, pl_list = [], [] - next_points_np = next_points.detach().round().clamp(0, 1).cpu().numpy().astype("int64") + next_points_np = ( + next_points.detach().round().clamp(0, 1).cpu().numpy().astype("int64") + ) for i in range(next_points_np.shape[0]): y_i, pl_i = self.objective_function(next_points_np[i]) y_list.append(float(y_i) if torch.is_tensor(y_i) else y_i) pl_list.append(float(pl_i) if torch.is_tensor(pl_i) else pl_i) - next_values = torch.tensor(y_list, dtype=torch.float32, device=self.device) # objective F - next_pl = torch.tensor(pl_list, dtype=torch.float32, device=self.device) # true pl + next_values = torch.tensor( + y_list, dtype=torch.float32, device=self.device + ) # objective F + next_pl = torch.tensor( + pl_list, dtype=torch.float32, device=self.device + ) # true pl t_evaluated = time.time() evaluation_history.append(next_values.detach().cpu().tolist()) @@ -218,15 +235,23 @@ def run(self): # --- Step 2b: record online diagnostics (pred vs. true) --- iter_metrics = [] # Map true pl to z-space using the same normalizer. - z_true_all = self.acquisition_function.normalizer.transform(next_pl).detach() + z_true_all = self.acquisition_function.normalizer.transform( + next_pl + ).detach() for i in range(next_pl.numel()): - mu_z = mu_z_pred[i]; sz = std_z_pred[i].clamp_min(1e-12) - mu_pl = mu_pl_pred[i]; sp = std_pl_pred[i].clamp_min(1e-12) + mu_z = mu_z_pred[i] + sz = std_z_pred[i].clamp_min(1e-12) + mu_pl = mu_pl_pred[i] + sp = std_pl_pred[i].clamp_min(1e-12) pl_t = next_pl[i] z_t = z_true_all[i] - r_pl = float(((pl_t - mu_pl) / sp).item()) # standardized residual in pl-space - r_z = float(((z_t - mu_z) / sz).item()) # standardized residual in z-space + r_pl = float( + ((pl_t - mu_pl) / sp).item() + ) # standardized residual in pl-space + r_z = float( + ((z_t - mu_z) / sz).item() + ) # standardized residual in z-space cover68 = abs(r_pl) <= 1.0 cover95 = abs(r_pl) <= 1.96 @@ -256,9 +281,9 @@ def run(self): self.pred_vs_true_history.append(iter_metrics) # --- Step 3: expand dataset with new points --- - self.X = torch.cat((self.X, next_points), dim=0) - self.y = torch.cat((self.y, next_values), dim=0) - self.pl = torch.cat((self.pl, next_pl), dim=0) + self.X = torch.cat((self.X, next_points), dim=0) + self.y = torch.cat((self.y, next_values), dim=0) + self.pl = torch.cat((self.pl, next_pl), dim=0) # --- Step 3b: refit normalizer on latest pl and warm-train GP --- t1 = time.time() @@ -288,18 +313,20 @@ def run(self): if nb.item() > self.best_value.item(): self.best_value = nb self.best_parameters = np_best.clone() - best_y_paras.append([ - self.best_value.item(), - self.best_parameters.detach().cpu().numpy().tolist() - ]) + best_y_paras.append( + [ + self.best_value.item(), + self.best_parameters.detach().cpu().numpy().tolist(), + ] + ) print(f"new code updated: {[best_y_paras[-1][0], best_y_paras[-1][1]]}") # --- Step 5: progress bar & logging --- pbar.set_description( - f'{self.description} (Best value: {self.best_value.item():.4f}), ' - f'time: suggest {t_next - t0:.3f}, eval {t_evaluated - t_next:.3f}, train {t_train - t1:.3f}' + f"{self.description} (Best value: {self.best_value.item():.4f}), " + f"time: suggest {t_next - t0:.3f}, eval {t_evaluated - t_next:.3f}, train {t_train - t1:.3f}" ) - with open('result.txt', 'a', encoding='utf-8') as f: + with open("result.txt", "a", encoding="utf-8") as f: f.write( f"{self.description} (Best value: {self.best_value.item():.6f}), " f"time: suggesting {t_next - t0:.6f}, evaluate {t_evaluated - t_next:.6f}, training {t_train - t1:.6f}\n" diff --git a/bayesian_optimization/chaincomplexembedding.py b/bayesian_optimization/chaincomplexembedding.py index 32bc292..f518485 100644 --- a/bayesian_optimization/chaincomplexembedding.py +++ b/bayesian_optimization/chaincomplexembedding.py @@ -51,20 +51,22 @@ class ChainComplexEmbedder(nn.Module): Activation used in the 2-layer MLP inside each message passing layer. """ - def __init__(self, - views_info: List[Dict[str, Any]], - d_model: int = 128, - num_layers: int = 4, - *, - view_aggr: str = "sum", - readout_types: Optional[List[str]] = None, - # per-layer options - num_bases: Optional[int] = 4, - norm: str = "sym", - residual: bool = True, - self_loop: bool = False, - dropout: float = 0.1, - act: Optional[nn.Module] = None): + def __init__( + self, + views_info: List[Dict[str, Any]], + d_model: int = 128, + num_layers: int = 4, + *, + view_aggr: str = "sum", + readout_types: Optional[List[str]] = None, + # per-layer options + num_bases: Optional[int] = 4, + norm: str = "sym", + residual: bool = True, + self_loop: bool = False, + dropout: float = 0.1, + act: Optional[nn.Module] = None, + ): super().__init__() assert view_aggr in ("sum", "mean") self.view_aggr = view_aggr @@ -82,27 +84,35 @@ def __init__(self, name = v.get("name", "view") nts = list(v.get("partite_classes", [])) rels = list(v.get("relations", [])) - self.views_info.append({"name": name, "partite_classes": nts, "relations": rels}) + self.views_info.append( + {"name": name, "partite_classes": nts, "relations": rels} + ) self.view_names.append(name) self.view_node_types[name] = nts self.view_relations[name] = rels all_types.update(nts) self.all_node_types: List[str] = sorted(all_types) - self.readout_types: List[str] = list(readout_types) if readout_types is not None else self.all_node_types + self.readout_types: List[str] = ( + list(readout_types) if readout_types is not None else self.all_node_types + ) # ---- Learnable initial token per node type ---- - self.type_tokens = nn.ParameterDict({ - t: nn.Parameter(torch.empty(1, self.d_model)) for t in self.all_node_types - }) + self.type_tokens = nn.ParameterDict( + {t: nn.Parameter(torch.empty(1, self.d_model)) for t in self.all_node_types} + ) for p in self.type_tokens.values(): nn.init.xavier_uniform_(p) # ---- Cross-view gates: one scalar per (layer, view) ---- - self.view_gates = nn.ParameterList([ - nn.ParameterDict({vn: nn.Parameter(torch.tensor(1.0)) for vn in self.view_names}) - for _ in range(self.num_layers) - ]) + self.view_gates = nn.ParameterList( + [ + nn.ParameterDict( + {vn: nn.Parameter(torch.tensor(1.0)) for vn in self.view_names} + ) + for _ in range(self.num_layers) + ] + ) # ---- Build message passing stacks (per layer, per view) ---- Layer = ChainComplexMessagePassingLayer @@ -111,12 +121,12 @@ def __init__(self, per_view = nn.ModuleDict() for vn in self.view_names: per_view[vn] = Layer( - node_types=self.all_node_types, # pass full set; layer skips empty types by 'sizes' + node_types=self.all_node_types, # pass full set; layer skips empty types by 'sizes' relations=self.view_relations[vn], in_dim=self.d_model, out_dim=self.d_model, num_bases=num_bases, - aggr="sum", # sum over relations; cross-view aggregation happens here + aggr="sum", # sum over relations; cross-view aggregation happens here norm=norm, residual=residual, self_loop=self_loop, @@ -133,7 +143,9 @@ def __init__(self, nn.Linear(read_dim, hidden), nn.GELU(), nn.Dropout(dropout), - nn.Linear(hidden, self.d_model), # change if you want a different final embedding size + nn.Linear( + hidden, self.d_model + ), # change if you want a different final embedding size ) # ===================== Packing utilities ===================== @@ -147,7 +159,9 @@ def _parse_relation_tag(tag: str) -> Tuple[str, str]: elif t.startswith("b"): t = t[1:] if "_" not in t: - raise ValueError(f"Bad relation tag '{tag}'; expected 'A_B' (optionally prefixed by 'b'/'co').") + raise ValueError( + f"Bad relation tag '{tag}'; expected 'A_B' (optionally prefixed by 'b'/'co')." + ) a, b = t.split("_", 1) return a.upper(), b.upper() @@ -191,8 +205,13 @@ def _concat_sparse_blockdiag( # empty sparse tensor with the correct global shape empty_idx = torch.empty((2, 0), dtype=torch.long, device=device) empty_val = torch.empty((0,), dtype=dtype, device=device) - return torch.sparse_coo_tensor(empty_idx, empty_val, size=(total_dst, total_src), - device=device, dtype=dtype).coalesce() + return torch.sparse_coo_tensor( + empty_idx, + empty_val, + size=(total_dst, total_src), + device=device, + dtype=dtype, + ).coalesce() rows = torch.cat(idx_rows, dim=0) cols = torch.cat(idx_cols, dim=0) @@ -271,9 +290,13 @@ def _pack_batch( src_sizes.append(batch_splits[src_t][b]) # zero degrees if the view is absent if batch_splits[src_t][b] > 0: - deg_src_list.append(torch.zeros(batch_splits[src_t][b], dtype=torch.float32)) + deg_src_list.append( + torch.zeros(batch_splits[src_t][b], dtype=torch.float32) + ) if batch_splits[dst_t][b] > 0: - deg_dst_list.append(torch.zeros(batch_splits[dst_t][b], dtype=torch.float32)) + deg_dst_list.append( + torch.zeros(batch_splits[dst_t][b], dtype=torch.float32) + ) continue A = vdict.get("adj", {}).get(r, None) @@ -296,11 +319,21 @@ def _pack_batch( deg_dst_list.append(ddst.detach().cpu()) # block-diagonal adjacency - A_batch = self._concat_sparse_blockdiag(parts, dst_sizes, src_sizes, device=device, dtype=dtype) + A_batch = self._concat_sparse_blockdiag( + parts, dst_sizes, src_sizes, device=device, dtype=dtype + ) # concatenated degrees - deg_src_cat = torch.cat(deg_src_list, dim=0) if deg_src_list else torch.zeros(0, dtype=torch.float32) - deg_dst_cat = torch.cat(deg_dst_list, dim=0) if deg_dst_list else torch.zeros(0, dtype=torch.float32) + deg_src_cat = ( + torch.cat(deg_src_list, dim=0) + if deg_src_list + else torch.zeros(0, dtype=torch.float32) + ) + deg_dst_cat = ( + torch.cat(deg_dst_list, dim=0) + if deg_dst_list + else torch.zeros(0, dtype=torch.float32) + ) deg_src_cat = deg_src_cat.to(device) deg_dst_cat = deg_dst_cat.to(device) @@ -318,8 +351,9 @@ def _pack_batch( # ===================== Pooling & init utilities ===================== @staticmethod - def _gather_global_sizes(views: Dict[str, Dict[str, Any]], - all_types: List[str]) -> Dict[str, int]: + def _gather_global_sizes( + views: Dict[str, Dict[str, Any]], all_types: List[str] + ) -> Dict[str, int]: """Collect total node counts per type across views (they should match; take max for robustness).""" totals: Dict[str, int] = {t: 0 for t in all_types} for v in views.values(): @@ -329,9 +363,9 @@ def _gather_global_sizes(views: Dict[str, Dict[str, Any]], return totals @staticmethod - def _build_initial_H(totals: Dict[str, int], - type_tokens: nn.ParameterDict, - device: torch.device) -> Dict[str, torch.Tensor]: + def _build_initial_H( + totals: Dict[str, int], type_tokens: nn.ParameterDict, device: torch.device + ) -> Dict[str, torch.Tensor]: """Create initial features by repeating per-type tokens according to total node counts.""" H: Dict[str, torch.Tensor] = {} for t, n in totals.items(): @@ -342,7 +376,9 @@ def _build_initial_H(totals: Dict[str, int], return H @staticmethod - def _pool_type_by_splits(H_t: torch.Tensor, splits: Optional[List[int]], mode: str = "mean") -> torch.Tensor: + def _pool_type_by_splits( + H_t: torch.Tensor, splits: Optional[List[int]], mode: str = "mean" + ) -> torch.Tensor: """ Per-type, per-sample pooling for a batched graph. @@ -374,9 +410,9 @@ def _pool_type_by_splits(H_t: torch.Tensor, splits: Optional[List[int]], mode: s # ===================== Forward ===================== - def forward(self, - views_or_list: Any, - batch_splits: Optional[Dict[str, List[int]]] = None) -> torch.Tensor: + def forward( + self, views_or_list: Any, batch_splits: Optional[Dict[str, List[int]]] = None + ) -> torch.Tensor: """ Accept either: • a list of per-sample views (output of RelationEncoder.encode for each sample), or @@ -389,14 +425,18 @@ def forward(self, # If a list is provided, pack it first if isinstance(views_or_list, list): - packed_views, batch_splits = self._pack_batch(views_or_list, device=device, dtype=dtype) + packed_views, batch_splits = self._pack_batch( + views_or_list, device=device, dtype=dtype + ) views = packed_views else: views = views_or_list # If no batch_splits are provided, assume a single-sample batch (B=1) if batch_splits is None: totals_single = self._gather_global_sizes(views, self.all_node_types) - batch_splits = {t: [int(totals_single.get(t, 0))] for t in self.all_node_types} + batch_splits = { + t: [int(totals_single.get(t, 0))] for t in self.all_node_types + } # (1) Collect total node counts per type and create initial features totals = self._gather_global_sizes(views, self.all_node_types) @@ -435,26 +475,26 @@ def forward(self, B = len(next(iter(batch_splits.values()))) if len(batch_splits) > 0 else 1 for t in self.readout_types: - splits_t = batch_splits.get(t, [0] * B) # fill zeros if a type never appears, to keep alignment + splits_t = batch_splits.get( + t, [0] * B + ) # fill zeros if a type never appears, to keep alignment pooled_t = self._pool_type_by_splits(H[t], splits_t, mode="mean") pooled_per_type.append(pooled_t) # (B, d) - G = torch.cat(pooled_per_type, dim=-1) if len(pooled_per_type) > 1 else pooled_per_type[0] # (B, d*) + G = ( + torch.cat(pooled_per_type, dim=-1) + if len(pooled_per_type) > 1 + else pooled_per_type[0] + ) # (B, d*) z = self.readout_mlp(G) # (B, d_model) return z - - - - - - - # --------------------------- # Message Passing Layer (fixed) # --------------------------- + def _parse_relation_tag_global(tag: str) -> Tuple[str, str]: """Return (SRC, DST) from 'A_B' (accepts 'bA_B'/'coA_B' and strips prefix).""" t = tag.strip() @@ -463,7 +503,9 @@ def _parse_relation_tag_global(tag: str) -> Tuple[str, str]: elif t.startswith("b"): t = t[1:] if "_" not in t: - raise ValueError(f"Bad relation tag '{tag}'; expected 'A_B' (optionally prefixed by 'b'/'co').") + raise ValueError( + f"Bad relation tag '{tag}'; expected 'A_B' (optionally prefixed by 'b'/'co')." + ) a, b = t.split("_", 1) return a.upper(), b.upper() @@ -477,19 +519,21 @@ class ChainComplexMessagePassingLayer(nn.Module): not view['sizes'], so that different views produce per-type tensors with **identical row counts**. """ - def __init__(self, - node_types: List[str], - relations: List[str], - in_dim: int, - out_dim: int, - *, - num_bases: Optional[int] = 4, - aggr: str = "sum", - norm: str = "sym", - residual: bool = True, - self_loop: bool = False, - dropout: float = 0.1, - act: Optional[nn.Module] = None): + def __init__( + self, + node_types: List[str], + relations: List[str], + in_dim: int, + out_dim: int, + *, + num_bases: Optional[int] = 4, + aggr: str = "sum", + norm: str = "sym", + residual: bool = True, + self_loop: bool = False, + dropout: float = 0.1, + act: Optional[nn.Module] = None, + ): super().__init__() assert aggr in ("sum", "mean") assert norm in ("sym", "dst", "none") @@ -506,41 +550,59 @@ def __init__(self, self.act = act if act is not None else nn.GELU() # Per-destination-type: LayerNorm (PreNorm), residual projection, update MLP, self-loop gate. - self.ln_in = nn.ModuleDict({t: nn.LayerNorm(self.in_dim) for t in self.node_types}) - self.res_proj = nn.ModuleDict({t: nn.Linear(self.in_dim, self.out_dim, bias=False) - for t in self.node_types}) + self.ln_in = nn.ModuleDict( + {t: nn.LayerNorm(self.in_dim) for t in self.node_types} + ) + self.res_proj = nn.ModuleDict( + { + t: nn.Linear(self.in_dim, self.out_dim, bias=False) + for t in self.node_types + } + ) hidden = max(self.out_dim * 2, 64) - self.update_mlp = nn.ModuleDict({ - t: nn.Sequential( - nn.Linear(self.out_dim * 2, hidden, bias=True), - self.act, - nn.Dropout(self.dropout), - nn.Linear(hidden, self.out_dim, bias=True), - ) for t in self.node_types - }) + self.update_mlp = nn.ModuleDict( + { + t: nn.Sequential( + nn.Linear(self.out_dim * 2, hidden, bias=True), + self.act, + nn.Dropout(self.dropout), + nn.Linear(hidden, self.out_dim, bias=True), + ) + for t in self.node_types + } + ) if self.self_loop: - self.self_gate = nn.ParameterDict({t: nn.Parameter(torch.tensor(1.0)) for t in self.node_types}) + self.self_gate = nn.ParameterDict( + {t: nn.Parameter(torch.tensor(1.0)) for t in self.node_types} + ) # Relation weights: basis or per-relation if self.num_bases is None or self.num_bases >= len(self.relations): self.weight_mode = "per_relation" - self.rel_W = nn.ParameterDict({ - r: nn.Parameter(torch.empty(self.in_dim, self.out_dim)) for r in self.relations - }) + self.rel_W = nn.ParameterDict( + { + r: nn.Parameter(torch.empty(self.in_dim, self.out_dim)) + for r in self.relations + } + ) for p in self.rel_W.values(): nn.init.xavier_uniform_(p) else: self.weight_mode = "basis" B = self.num_bases - self.bases = nn.ParameterList([nn.Parameter(torch.empty(self.in_dim, self.out_dim)) for _ in range(B)]) + self.bases = nn.ParameterList( + [nn.Parameter(torch.empty(self.in_dim, self.out_dim)) for _ in range(B)] + ) for V in self.bases: nn.init.xavier_uniform_(V) - self.rel_coeff = nn.ParameterDict({ - r: nn.Parameter(torch.randn(B) / (B ** 0.5)) for r in self.relations - }) + self.rel_coeff = nn.ParameterDict( + {r: nn.Parameter(torch.randn(B) / (B**0.5)) for r in self.relations} + ) # Relation gates (scalar) - self.rel_gate = nn.ParameterDict({r: nn.Parameter(torch.tensor(1.0)) for r in self.relations}) + self.rel_gate = nn.ParameterDict( + {r: nn.Parameter(torch.tensor(1.0)) for r in self.relations} + ) self.dropout_out = nn.Dropout(self.dropout) # ---- helpers ---- @@ -549,7 +611,9 @@ def _get_W(self, r: str) -> torch.Tensor: return self.rel_W[r] # basis coeff = self.rel_coeff[r] # (B,) - W = torch.zeros((self.in_dim, self.out_dim), device=coeff.device, dtype=self.bases[0].dtype) + W = torch.zeros( + (self.in_dim, self.out_dim), device=coeff.device, dtype=self.bases[0].dtype + ) for a, V in zip(coeff, self.bases): W = W + a * V return W @@ -568,11 +632,13 @@ def _safe_inv(x: torch.Tensor) -> torch.Tensor: out[mask] = x[mask].reciprocal() return out - def _spmm_norm(self, - A: torch.Tensor, # sparse COO (|dst| x |src|) - X_src: torch.Tensor, # (|src| x out_dim) - deg_src: torch.Tensor, # (|src|,) - deg_dst: torch.Tensor) -> torch.Tensor: # (|dst|,) + def _spmm_norm( + self, + A: torch.Tensor, # sparse COO (|dst| x |src|) + X_src: torch.Tensor, # (|src| x out_dim) + deg_src: torch.Tensor, # (|src|,) + deg_dst: torch.Tensor, + ) -> torch.Tensor: # (|dst|,) """Apply normalization + SpMM for one relation.""" if self.norm == "none": msg = torch.sparse.mm(A, X_src) @@ -582,16 +648,16 @@ def _spmm_norm(self, dinv = self._safe_inv(deg_dst).unsqueeze(-1) # (|dst|,1) return msg * dinv else: # 'sym' - dsrc = self._safe_inv_sqrt(deg_src).unsqueeze(-1) # (|src|,1) + dsrc = self._safe_inv_sqrt(deg_src).unsqueeze(-1) # (|src|,1) Xn = X_src * dsrc msg = torch.sparse.mm(A, Xn) - ddst = self._safe_inv_sqrt(deg_dst).unsqueeze(-1) # (|dst|,1) + ddst = self._safe_inv_sqrt(deg_dst).unsqueeze(-1) # (|dst|,1) return msg * ddst # ---- forward ---- - def forward(self, - H: Dict[str, torch.Tensor], - view: Dict[str, dict]) -> Dict[str, torch.Tensor]: + def forward( + self, H: Dict[str, torch.Tensor], view: Dict[str, dict] + ) -> Dict[str, torch.Tensor]: """ H: {type: (N_type_global, in_dim)} view: {'adj': {relation_tag: sparse_coo}, 'deg': {relation_tag: (deg_src, deg_dst)}, ...} @@ -602,11 +668,14 @@ def forward(self, device = next(self.parameters()).device # GLOBAL counts from H, not from view['sizes'] - N: Dict[str, int] = {t: (H[t].shape[0] if t in H else 0) for t in self.node_types} + N: Dict[str, int] = { + t: (H[t].shape[0] if t in H else 0) for t in self.node_types + } # Aggregation buffers per destination type (GLOBAL shapes) agg_msgs: Dict[str, torch.Tensor] = { - t: torch.zeros((N.get(t, 0), self.out_dim), device=device) for t in self.node_types + t: torch.zeros((N.get(t, 0), self.out_dim), device=device) + for t in self.node_types } rel_counts: Dict[str, int] = {t: 0 for t in self.node_types} @@ -625,8 +694,8 @@ def forward(self, for r in self.relations: if "adj" not in view or r not in view["adj"]: continue # relation absent - A = view["adj"][r] # (|dst_global| x |src_global|) sparse COO - deg_src, deg_dst = view["deg"][r] # 1D tensors + A = view["adj"][r] # (|dst_global| x |src_global|) sparse COO + deg_src, deg_dst = view["deg"][r] # 1D tensors src, dst = _parse_relation_tag_global(r) n_src = N.get(src, 0) @@ -635,15 +704,18 @@ def forward(self, continue # no-op # Sanity check: adjacency must match global shapes - assert A.size(0) == n_dst and A.size(1) == n_src, \ + assert A.size(0) == n_dst and A.size(1) == n_src, ( f"Adjacency shape mismatch for relation {r}: got {tuple(A.size())}, expected ({n_dst},{n_src})" + ) # 1) Linear map on src - W_r = self._get_W(r) # (in_dim x out_dim) - X_src = H[src] @ W_r # (|src| x out_dim) + W_r = self._get_W(r) # (in_dim x out_dim) + X_src = H[src] @ W_r # (|src| x out_dim) # 2) Normalization + SpMM - msg = self._spmm_norm(A, X_src, deg_src.to(device), deg_dst.to(device)) # (|dst| x out_dim) + msg = self._spmm_norm( + A, X_src, deg_src.to(device), deg_dst.to(device) + ) # (|dst| x out_dim) # 3) Relation gate gamma = self.rel_gate[r] @@ -675,7 +747,7 @@ def forward(self, out[t] = torch.zeros((0, self.out_dim), device=device) continue upd_in = torch.cat([proj_dst[t], agg_msgs[t]], dim=-1) # (|t|, 2*out_dim) - upd = self.update_mlp[t](upd_in) # (|t|, out_dim) + upd = self.update_mlp[t](upd_in) # (|t|, out_dim) upd = self.dropout_out(upd) out[t] = proj_dst[t] + upd if self.residual else upd return out @@ -685,6 +757,7 @@ def forward(self, # Embedder (fixed) # --------------------------- + def _cum_offsets(sizes: List[int]) -> List[int]: """Compute cumulative offsets for a list of segment sizes.""" offs = [0] @@ -705,20 +778,22 @@ class ChainComplexEmbedder(nn.Module): • per-type global counts are computed from **batch_splits** (sum) to initialize features. """ - def __init__(self, - views_info: List[Dict[str, Any]], - d_model: int = 128, - num_layers: int = 4, - *, - view_aggr: str = "sum", - readout_types: Optional[List[str]] = None, - # per-layer options - num_bases: Optional[int] = 4, - norm: str = "sym", - residual: bool = True, - self_loop: bool = False, - dropout: float = 0.1, - act: Optional[nn.Module] = None): + def __init__( + self, + views_info: List[Dict[str, Any]], + d_model: int = 128, + num_layers: int = 4, + *, + view_aggr: str = "sum", + readout_types: Optional[List[str]] = None, + # per-layer options + num_bases: Optional[int] = 4, + norm: str = "sym", + residual: bool = True, + self_loop: bool = False, + dropout: float = 0.1, + act: Optional[nn.Module] = None, + ): super().__init__() assert view_aggr in ("sum", "mean") self.view_aggr = view_aggr @@ -736,27 +811,35 @@ def __init__(self, name = v.get("name", "view") nts = list(v.get("partite_classes", [])) rels = list(v.get("relations", [])) - self.views_info.append({"name": name, "partite_classes": nts, "relations": rels}) + self.views_info.append( + {"name": name, "partite_classes": nts, "relations": rels} + ) self.view_names.append(name) self.view_node_types[name] = nts self.view_relations[name] = rels all_types.update(nts) self.all_node_types: List[str] = sorted(all_types) - self.readout_types: List[str] = list(readout_types) if readout_types is not None else self.all_node_types + self.readout_types: List[str] = ( + list(readout_types) if readout_types is not None else self.all_node_types + ) # Type tokens - self.type_tokens = nn.ParameterDict({ - t: nn.Parameter(torch.empty(1, self.d_model)) for t in self.all_node_types - }) + self.type_tokens = nn.ParameterDict( + {t: nn.Parameter(torch.empty(1, self.d_model)) for t in self.all_node_types} + ) for p in self.type_tokens.values(): nn.init.xavier_uniform_(p) # View gates: one scalar per (layer, view) - self.view_gates = nn.ParameterList([ - nn.ParameterDict({vn: nn.Parameter(torch.tensor(1.0)) for vn in self.view_names}) - for _ in range(self.num_layers) - ]) + self.view_gates = nn.ParameterList( + [ + nn.ParameterDict( + {vn: nn.Parameter(torch.tensor(1.0)) for vn in self.view_names} + ) + for _ in range(self.num_layers) + ] + ) # Per-layer, per-view message passing modules Layer = ChainComplexMessagePassingLayer @@ -801,7 +884,9 @@ def _parse_relation_tag(tag: str) -> Tuple[str, str]: elif t.startswith("b"): t = t[1:] if "_" not in t: - raise ValueError(f"Bad relation tag '{tag}'; expected 'A_B' (optionally prefixed by 'b'/'co').") + raise ValueError( + f"Bad relation tag '{tag}'; expected 'A_B' (optionally prefixed by 'b'/'co')." + ) a, b = t.split("_", 1) return a.upper(), b.upper() @@ -844,8 +929,13 @@ def _concat_sparse_blockdiag( if len(vals) == 0: empty_idx = torch.empty((2, 0), dtype=torch.long, device=device) empty_val = torch.empty((0,), dtype=dtype, device=device) - return torch.sparse_coo_tensor(empty_idx, empty_val, size=(total_dst, total_src), - device=device, dtype=dtype).coalesce() + return torch.sparse_coo_tensor( + empty_idx, + empty_val, + size=(total_dst, total_src), + device=device, + dtype=dtype, + ).coalesce() rows = torch.cat(idx_rows, dim=0) cols = torch.cat(idx_cols, dim=0) @@ -917,7 +1007,9 @@ def _pack_batch( for b in range(B): vdict = sample_views_list[b].get(vn, None) - blk_src = batch_splits[src_t][b] # ALWAYS use global per-sample block size + blk_src = batch_splits[src_t][ + b + ] # ALWAYS use global per-sample block size blk_dst = batch_splits[dst_t][b] # adjacency block (can be None) @@ -935,11 +1027,27 @@ def _pack_batch( dsrc = dsrc.detach().cpu() ddst = ddst.detach().cpu() if dsrc.numel() < blk_src: - dsrc = torch.cat([dsrc, torch.zeros(blk_src - dsrc.numel(), dtype=torch.float32)], dim=0) + dsrc = torch.cat( + [ + dsrc, + torch.zeros( + blk_src - dsrc.numel(), dtype=torch.float32 + ), + ], + dim=0, + ) elif dsrc.numel() > blk_src: dsrc = dsrc[:blk_src] if ddst.numel() < blk_dst: - ddst = torch.cat([ddst, torch.zeros(blk_dst - ddst.numel(), dtype=torch.float32)], dim=0) + ddst = torch.cat( + [ + ddst, + torch.zeros( + blk_dst - ddst.numel(), dtype=torch.float32 + ), + ], + dim=0, + ) elif ddst.numel() > blk_dst: ddst = ddst[:blk_dst] @@ -947,7 +1055,9 @@ def _pack_batch( deg_dst_list.append(ddst) # block-diagonal adjacency (GLOBAL shapes) - A_batch = self._concat_sparse_blockdiag(parts, dst_sizes, src_sizes, device=device, dtype=dtype) + A_batch = self._concat_sparse_blockdiag( + parts, dst_sizes, src_sizes, device=device, dtype=dtype + ) # concatenated degrees deg_src_cat = torch.cat(deg_src_list, dim=0).to(device) @@ -957,7 +1067,7 @@ def _pack_batch( deg_packed[r] = (deg_src_cat, deg_dst_cat) packed_views[vn] = { - "sizes": totals_v, # kept for reference; layer does not rely on it for allocation + "sizes": totals_v, # kept for reference; layer does not rely on it for allocation "adj": adj_packed, "deg": deg_packed, } @@ -965,9 +1075,11 @@ def _pack_batch( return packed_views, batch_splits @staticmethod - def _build_initial_H_from_splits(batch_splits: Dict[str, List[int]], - type_tokens: nn.ParameterDict, - device: torch.device) -> Dict[str, torch.Tensor]: + def _build_initial_H_from_splits( + batch_splits: Dict[str, List[int]], + type_tokens: nn.ParameterDict, + device: torch.device, + ) -> Dict[str, torch.Tensor]: """Create initial features by repeating per-type tokens; counts from sum of batch_splits.""" H: Dict[str, torch.Tensor] = {} for t, splits in batch_splits.items(): @@ -979,12 +1091,18 @@ def _build_initial_H_from_splits(batch_splits: Dict[str, List[int]], return H @staticmethod - def _pool_type_by_splits(H_t: torch.Tensor, splits: Optional[List[int]], mode: str = "mean") -> torch.Tensor: + def _pool_type_by_splits( + H_t: torch.Tensor, splits: Optional[List[int]], mode: str = "mean" + ) -> torch.Tensor: """Per-type, per-sample pooling for a batched graph.""" if splits is None: if H_t.numel() == 0: return H_t.new_zeros((1, H_t.shape[1])) - return H_t.mean(dim=0, keepdim=True) if mode == "mean" else H_t.sum(dim=0, keepdim=True) + return ( + H_t.mean(dim=0, keepdim=True) + if mode == "mean" + else H_t.sum(dim=0, keepdim=True) + ) B = len(splits) offs = _cum_offsets(splits) @@ -999,9 +1117,9 @@ def _pool_type_by_splits(H_t: torch.Tensor, splits: Optional[List[int]], mode: s # ---------- Forward ---------- - def forward(self, - views_or_list: Any, - batch_splits: Optional[Dict[str, List[int]]] = None) -> torch.Tensor: + def forward( + self, views_or_list: Any, batch_splits: Optional[Dict[str, List[int]]] = None + ) -> torch.Tensor: """ Accept either: • a list of per-sample views (output of RelationEncoder.encode for each sample), or @@ -1014,10 +1132,14 @@ def forward(self, # If a list is provided, pack it first if isinstance(views_or_list, list): - packed_views, batch_splits = self._pack_batch(views_or_list, device=device, dtype=dtype) + packed_views, batch_splits = self._pack_batch( + views_or_list, device=device, dtype=dtype + ) views = packed_views # Build initial features using global counts from batch_splits - H = self._build_initial_H_from_splits(batch_splits, self.type_tokens, device=device) + H = self._build_initial_H_from_splits( + batch_splits, self.type_tokens, device=device + ) else: views = views_or_list # If no batch_splits are provided, assume B=1 and infer counts from views (best effort) @@ -1026,9 +1148,15 @@ def forward(self, for t in self.all_node_types: totals_single[t] = 0 for v in views.values(): - totals_single[t] = max(totals_single[t], int(v.get("sizes", {}).get(t, 0))) - batch_splits = {t: [totals_single.get(t, 0)] for t in self.all_node_types} - H = self._build_initial_H_from_splits(batch_splits, self.type_tokens, device=device) + totals_single[t] = max( + totals_single[t], int(v.get("sizes", {}).get(t, 0)) + ) + batch_splits = { + t: [totals_single.get(t, 0)] for t in self.all_node_types + } + H = self._build_initial_H_from_splits( + batch_splits, self.type_tokens, device=device + ) # L stacked layers; per layer: run every view, then aggregate across views for li, per_view in enumerate(self.layers): @@ -1062,10 +1190,16 @@ def forward(self, B = len(next(iter(batch_splits.values()))) if len(batch_splits) > 0 else 1 for t in self.readout_types: - splits_t = batch_splits.get(t, [0] * B) # fill zeros if a type never appears + splits_t = batch_splits.get( + t, [0] * B + ) # fill zeros if a type never appears pooled_t = self._pool_type_by_splits(H[t], splits_t, mode="mean") pooled_per_type.append(pooled_t) # (B, d) - G = torch.cat(pooled_per_type, dim=-1) if len(pooled_per_type) > 1 else pooled_per_type[0] # (B, d*) + G = ( + torch.cat(pooled_per_type, dim=-1) + if len(pooled_per_type) > 1 + else pooled_per_type[0] + ) # (B, d*) z = self.readout_mlp(G) # (B, d_model) return z diff --git a/bayesian_optimization/encoder.py b/bayesian_optimization/encoder.py index 921b88a..838af51 100644 --- a/bayesian_optimization/encoder.py +++ b/bayesian_optimization/encoder.py @@ -1,6 +1,7 @@ """ - This file defines encoder that converts the input code into different representations +This file defines encoder that converts the input code into different representations """ + from __future__ import annotations from typing import List, Dict, Any, Tuple, Optional import numpy as np @@ -12,54 +13,56 @@ from bayesian_optimization.gadget import * -class CSSEncoder(): - def __init__(self,info,mode='graph',grakel_use = False,logical=False,cc_patern=[]): +class CSSEncoder: + def __init__( + self, info, mode="graph", grakel_use=False, logical=False, cc_patern=[] + ): """ - n: number of qubits, n = hx.shape[1] = hz.shape[1] - nx: number of x stabilizers, nx = hx.shape[0] - nz: number of z stabilizers, nz = hz.shape[0] - mode: 'graph' or 'combinatorial_complex' + n: number of qubits, n = hx.shape[1] = hz.shape[1] + nx: number of x stabilizers, nx = hx.shape[0] + nz: number of z stabilizers, nz = hz.shape[0] + mode: 'graph' or 'combinatorial_complex' """ self.logical = logical - self.mode=mode - if mode == 'graph': - n = info['n'] - nx = info['nx'] - nz = info['nz'] - self.encoder = GraphEncoder(n,nx,nz,grakel_use=grakel_use,logical=logical) - elif mode == 'combinatorial_complex': - n = info['n'] - nx = info['nx'] - nz = info['nz'] - self.encoder = CombinatorialComplexEncoder_(n,nx,nz) - elif mode == 'chain_complex': + self.mode = mode + if mode == "graph": + n = info["n"] + nx = info["nx"] + nz = info["nz"] + self.encoder = GraphEncoder( + n, nx, nz, grakel_use=grakel_use, logical=logical + ) + elif mode == "combinatorial_complex": + n = info["n"] + nx = info["nx"] + nz = info["nz"] + self.encoder = CombinatorialComplexEncoder_(n, nx, nz) + elif mode == "chain_complex": self.encoder = ChainComplexGlobalFeaturesEncoder(info) - elif mode == 'relations': + elif mode == "relations": self.encoder = RelationEncoder(info) - def encode(self,c): - if self.mode in ['chain_complex','relations']: - return self.encoder.encode(c) + + def encode(self, c): + if self.mode in ["chain_complex", "relations"]: + return self.encoder.encode(c) if self.logical: hx = c.hx hz = c.hz lx = c.lx lz = c.lz - return self.encoder.encode(hx,hz,lx,lz) + return self.encoder.encode(hx, hz, lx, lz) else: hx = c.hx hz = c.hz - return self.encoder.encode(hx,hz) - - + return self.encoder.encode(hx, hz) - -class GraphEncoder(): - def __init__(self,n,nx,nz,grakel_use=False,logical=False): +class GraphEncoder: + def __init__(self, n, nx, nz, grakel_use=False, logical=False): """ - n: number of qubits, n = hx.shape[1] = hz.shape[1] - nx: number of x stabilizers, nx = hx.shape[0] - nz: number of z stabilizers, nz = hz.shape[0] + n: number of qubits, n = hx.shape[1] = hz.shape[1] + nx: number of x stabilizers, nx = hx.shape[0] + nz: number of z stabilizers, nz = hz.shape[0] """ self.n = n self.nx = nx @@ -68,112 +71,130 @@ def __init__(self,n,nx,nz,grakel_use=False,logical=False): if grakel_use: pass self.logical = logical - def preprocess_graph(self,adj_matrix: np.ndarray, node_onehots: np.ndarray): + + def preprocess_graph(self, adj_matrix: np.ndarray, node_onehots: np.ndarray): """ adj_matrix: np.ndarray of shape (N, N) node_onehots: np.ndarray of shape (N, 5) """ - + src, dst = np.nonzero(adj_matrix) edge_index = torch.tensor([src, dst], dtype=torch.long) # shape [2, E] + x = torch.tensor(node_onehots, dtype=torch.float) # shape [N, 5] - x = torch.tensor(node_onehots, dtype=torch.float) # shape [N, 5] - - data = Data(x=x, edge_index=edge_index) return data - - def encode(self,hx,hz,lx=None,lz=None): - - # adjacency_matrix[self.n+self.nx+j][i] = 1 + + def encode(self, hx, hz, lx=None, lz=None): + + # adjacency_matrix[self.n+self.nx+j][i] = 1 # print(f'adjacency_matrix:{adjacency_matrix}') # edge_index = dense_to_sparse(adjacency_matrix)[0] # return edge_index - if self.logical==False: - adjacency_matrix = np.zeros((self.n+self.nx+self.nz,self.n+self.nx+self.nz)) - + if self.logical == False: + adjacency_matrix = np.zeros( + (self.n + self.nx + self.nz, self.n + self.nx + self.nz) + ) + for i in range(self.n): for j in range(self.nx): if hx[j][i] == 1: - adjacency_matrix[i][self.n+j] = 1 - adjacency_matrix[self.n+j][i] = 1 + adjacency_matrix[i][self.n + j] = 1 + adjacency_matrix[self.n + j][i] = 1 for j in range(self.nz): if hz[j][i] == 1: - adjacency_matrix[i][self.n+self.nx+j] = 1 - adjacency_matrix[self.n+self.nx+j][i] = 1 + adjacency_matrix[i][self.n + self.nx + j] = 1 + adjacency_matrix[self.n + self.nx + j][i] = 1 if not self.grakel_use: - return torch.tensor(adjacency_matrix) else: - - node_labels = {} for i in range(self.n): - node_labels[i]='0' - for i in range(self.n,self.n+self.nx): - node_labels[i]='+' - for i in range(self.n+self.nx,self.n+self.nx+self.nz): - node_labels[i]='-' - + node_labels[i] = "0" + for i in range(self.n, self.n + self.nx): + node_labels[i] = "+" + for i in range(self.n + self.nx, self.n + self.nx + self.nz): + node_labels[i] = "-" + # print('adjacency_matrix') # print(adjacency_matrix) - g = Graph(initialization_object=adjacency_matrix,node_labels=node_labels,graph_format='adjacency',construct_labels=False) + g = Graph( + initialization_object=adjacency_matrix, + node_labels=node_labels, + graph_format="adjacency", + construct_labels=False, + ) # print(g) return g else: - total_nodes = self.n+self.nx+self.nz+lx.shape[0]+lz.shape[0] - adjacency_matrix = np.zeros((total_nodes,total_nodes)) + total_nodes = self.n + self.nx + self.nz + lx.shape[0] + lz.shape[0] + adjacency_matrix = np.zeros((total_nodes, total_nodes)) for i in range(self.n): for j in range(self.nx): if hx[j][i] == 1: - adjacency_matrix[i][self.n+j] = 1 - adjacency_matrix[self.n+j][i] = 1 + adjacency_matrix[i][self.n + j] = 1 + adjacency_matrix[self.n + j][i] = 1 for j in range(self.nz): if hz[j][i] == 1: - adjacency_matrix[i][self.n+self.nx+j] = 1 - adjacency_matrix[self.n+self.nx+j][i] = 1 + adjacency_matrix[i][self.n + self.nx + j] = 1 + adjacency_matrix[self.n + self.nx + j][i] = 1 for j in range(lx.shape[0]): - if lx[j,i]==1: - adjacency_matrix[i][self.n+self.nx+self.nz+j]=1 - adjacency_matrix[self.n+self.nx+self.nz+j][i]=1 + if lx[j, i] == 1: + adjacency_matrix[i][self.n + self.nx + self.nz + j] = 1 + adjacency_matrix[self.n + self.nx + self.nz + j][i] = 1 for j in range(lz.shape[0]): - if lz[j,i]==1: - adjacency_matrix[i][self.n+self.nx+self.nz+lx.shape[0]+j]=1 - adjacency_matrix[self.n+self.nx+self.nz+lx.shape[0]+j][i]=1 + if lz[j, i] == 1: + adjacency_matrix[i][ + self.n + self.nx + self.nz + lx.shape[0] + j + ] = 1 + adjacency_matrix[self.n + self.nx + self.nz + lx.shape[0] + j][ + i + ] = 1 if not self.grakel_use: node_onehots = torch.zeros((total_nodes, 5), dtype=torch.float) - node_onehots[0 : self.n, 0] = 1.0 - node_onehots[self.n : self.n + self.nx, 1] = 1.0 + node_onehots[0 : self.n, 0] = 1.0 + node_onehots[self.n : self.n + self.nx, 1] = 1.0 node_onehots[self.n + self.nx : self.n + self.nx + self.nz, 2] = 1.0 - node_onehots[self.n + self.nx + self.nz : self.n + self.nx + self.nz + lx.shape[0], 3] = 1.0 - node_onehots[self.n + self.nx + self.nz + lx.shape[0] : total_nodes, 4] = 1.0 - return self.preprocess_graph(adjacency_matrix,node_onehots) + node_onehots[ + self.n + self.nx + self.nz : self.n + + self.nx + + self.nz + + lx.shape[0], + 3, + ] = 1.0 + node_onehots[ + self.n + self.nx + self.nz + lx.shape[0] : total_nodes, 4 + ] = 1.0 + return self.preprocess_graph(adjacency_matrix, node_onehots) else: - - node_labels = {} for i in range(self.n): - node_labels[i]='0' - for i in range(self.n,self.n+self.nx): - node_labels[i]='+' - for i in range(self.n+self.nx,self.n+self.nx+self.nz): - node_labels[i]='-' - for i in range(self.n+self.nx+self.nz,self.n+self.nx+self.nz+lx.shape[0]): - node_labels[i]='x' - for i in range(self.n+self.nx+self.nz+lx.shape[0],self.n+self.nx+self.nz+lx.shape[0]+lz.shape[0]): - node_labels[i]='z' - + node_labels[i] = "0" + for i in range(self.n, self.n + self.nx): + node_labels[i] = "+" + for i in range(self.n + self.nx, self.n + self.nx + self.nz): + node_labels[i] = "-" + for i in range( + self.n + self.nx + self.nz, self.n + self.nx + self.nz + lx.shape[0] + ): + node_labels[i] = "x" + for i in range( + self.n + self.nx + self.nz + lx.shape[0], + self.n + self.nx + self.nz + lx.shape[0] + lz.shape[0], + ): + node_labels[i] = "z" + # print('adjacency_matrix') # print(adjacency_matrix) - g = Graph(initialization_object=adjacency_matrix,node_labels=node_labels,graph_format='adjacency',construct_labels=False) + g = Graph( + initialization_object=adjacency_matrix, + node_labels=node_labels, + graph_format="adjacency", + construct_labels=False, + ) # print(g) return g - - - - - # optional SciPy sparse backend @@ -196,13 +217,12 @@ def _vstack_bool_int(*rows: np.ndarray) -> np.ndarray: """vstack rows after binarization to {0,1} int64.""" rows_b = [_to_bool_int(r) for r in rows if r is not None and r.size > 0] if not rows_b: - return np.zeros((0, rows[0].shape[1]), dtype=np.int64) # assume widths match if any + return np.zeros( + (0, rows[0].shape[1]), dtype=np.int64 + ) # assume widths match if any return np.vstack(rows_b).astype(np.int64, copy=False) - - - def _to_bool_int(a: np.ndarray) -> np.ndarray: """Map any ndarray to {0,1} int64 (GF(2) semantics).""" a = np.asarray(a) @@ -219,13 +239,15 @@ def _vstack_bool_int(*rows: np.ndarray) -> np.ndarray: return np.zeros((0, 0), dtype=np.int64) width = rows_b[0].shape[1] for rb in rows_b: - assert rb.shape[1] == width, "All stacked matrices must have the same number of columns" + assert rb.shape[1] == width, ( + "All stacked matrices must have the same number of columns" + ) return np.vstack(rows_b).astype(np.int64, copy=False) -def _np_to_sparse_coo(M: np.ndarray, - device: Optional[torch.device], - dtype: torch.dtype) -> torch.Tensor: +def _np_to_sparse_coo( + M: np.ndarray, device: Optional[torch.device], dtype: torch.dtype +) -> torch.Tensor: """ Convert 0/1 numpy array to torch.sparse_coo_tensor of shape M.shape on device. Values are 1.0 (dtype provided). Always coalesced. @@ -237,10 +259,14 @@ def _np_to_sparse_coo(M: np.ndarray, if nnz == 0: idx = torch.empty((2, 0), dtype=torch.long, device=device) val = torch.empty((0,), dtype=dtype, device=device) - return torch.sparse_coo_tensor(idx, val, size=(m, n), device=device, dtype=dtype).coalesce() + return torch.sparse_coo_tensor( + idx, val, size=(m, n), device=device, dtype=dtype + ).coalesce() idx = torch.from_numpy(np.vstack([rows, cols])).long().to(device) val = torch.ones((nnz,), dtype=dtype, device=device) - return torch.sparse_coo_tensor(idx, val, size=(m, n), device=device, dtype=dtype).coalesce() + return torch.sparse_coo_tensor( + idx, val, size=(m, n), device=device, dtype=dtype + ).coalesce() class RelationEncoder: @@ -281,10 +307,12 @@ class RelationEncoder: NODE_SET = {"SX", "SZ", "DQ", "LX", "LZ", "AX", "AZ"} - def __init__(self, - views_info: Optional[List[Dict[str, Any]]] = None, - device: Optional[torch.device] = None, - dtype: torch.dtype = torch.float32): + def __init__( + self, + views_info: Optional[List[Dict[str, Any]]] = None, + device: Optional[torch.device] = None, + dtype: torch.dtype = torch.float32, + ): self.views_info = views_info or [] self.device = device self.dtype = dtype @@ -314,25 +342,39 @@ def encode(self, c) -> Dict[str, Dict[str, Any]]: AZ = _vstack_bool_int(Hz, Lz) # rows = mz + kz # Sanity on widths - assert Hx.shape[1] == n and Hz.shape[1] == n and Lx.shape[1] == n and Lz.shape[1] == n, \ - "All Hx/Hz/Lx/Lz must have width n" + assert ( + Hx.shape[1] == n + and Hz.shape[1] == n + and Lx.shape[1] == n + and Lz.shape[1] == n + ), "All Hx/Hz/Lx/Lz must have width n" # Sizes per node type sizes_all = { - "SX": mx, "SZ": mz, "DQ": n, - "LX": kx, "LZ": kz, - "AX": AX.shape[0], "AZ": AZ.shape[0], + "SX": mx, + "SZ": mz, + "DQ": n, + "LX": kx, + "LZ": kz, + "AX": AX.shape[0], + "AZ": AZ.shape[0], } # Primitive matrices by (SRC, DST) with our left-multiply convention # Shape is (|DST|, |SRC|) prim_np: Dict[Tuple[str, str], np.ndarray] = { - ("SX", "DQ"): Hx.T, ("DQ", "SX"): Hx, - ("SZ", "DQ"): Hz.T, ("DQ", "SZ"): Hz, - ("LX", "DQ"): Lx.T, ("DQ", "LX"): Lx, - ("LZ", "DQ"): Lz.T, ("DQ", "LZ"): Lz, - ("AX", "DQ"): AX.T, ("DQ", "AX"): AX, - ("AZ", "DQ"): AZ.T, ("DQ", "AZ"): AZ, + ("SX", "DQ"): Hx.T, + ("DQ", "SX"): Hx, + ("SZ", "DQ"): Hz.T, + ("DQ", "SZ"): Hz, + ("LX", "DQ"): Lx.T, + ("DQ", "LX"): Lx, + ("LZ", "DQ"): Lz.T, + ("DQ", "LZ"): Lz, + ("AX", "DQ"): AX.T, + ("DQ", "AX"): AX, + ("AZ", "DQ"): AZ.T, + ("DQ", "AZ"): AZ, } out: Dict[str, Dict[str, Any]] = {} @@ -383,8 +425,9 @@ def _validate_partite(self, partite: List[str]) -> None: # if ("LX" in partite and "AX" in partite) or ("LZ" in partite and "AZ" in partite): # pass - def _build_relation_matrix(self, tag: str, - prim_np: Dict[Tuple[str, str], np.ndarray]) -> Tuple[np.ndarray, Tuple[str, str]]: + def _build_relation_matrix( + self, tag: str, prim_np: Dict[Tuple[str, str], np.ndarray] + ) -> Tuple[np.ndarray, Tuple[str, str]]: """ Return (numpy matrix, (src,dst)) for relation tag. @@ -398,7 +441,9 @@ def _build_relation_matrix(self, tag: str, raw = tag.strip() if raw.startswith("adj"): - raise NotImplementedError(f"Relation '{tag}': 'adj*' builders are reserved for later.") + raise NotImplementedError( + f"Relation '{tag}': 'adj*' builders are reserved for later." + ) if raw.startswith("co"): core = raw[2:] @@ -425,79 +470,87 @@ def _parse_A_B(core: str) -> Tuple[str, str]: return a.strip().upper(), b.strip().upper() @staticmethod - def _lookup_primitive(src: str, dst: str, - prim_np: Dict[Tuple[str, str], np.ndarray], - tag: str) -> np.ndarray: + def _lookup_primitive( + src: str, dst: str, prim_np: Dict[Tuple[str, str], np.ndarray], tag: str + ) -> np.ndarray: key = (src, dst) if key not in prim_np: - raise ValueError(f"Relation '{tag}': unsupported primitive mapping '{src}->{dst}'.") + raise ValueError( + f"Relation '{tag}': unsupported primitive mapping '{src}->{dst}'." + ) return prim_np[key] - - -class CombinatorialComplexEncoder_(): - def __init__(self,n,nx,nz): +class CombinatorialComplexEncoder_: + def __init__(self, n, nx, nz): """ - n: number of qubits, n = hx.shape[1] = hz.shape[1] - nx: number of x stabilizers, nx = hx.shape[0] - nz: number of z stabilizers, nz = hz.shape[0] + n: number of qubits, n = hx.shape[1] = hz.shape[1] + nx: number of x stabilizers, nx = hx.shape[0] + nz: number of z stabilizers, nz = hz.shape[0] """ self.n_c0 = nx self.n_c1 = n self.n_c2 = nz - - def encode(self,hx,hz) -> List[csr_matrix]: + + def encode(self, hx, hz) -> List[csr_matrix]: """ - return Tensor: - tensor([coA01,coA02,A10,coA12,A20,A21,B01,B02,B12]) + return Tensor: + tensor([coA01,coA02,A10,coA12,A20,A21,B01,B02,B12]) """ CSS_cc = CombinatorialComplex() incidence_01 = [[] for _ in range(self.n_c1)] incidence_02 = [[] for _ in range(self.n_c2)] - for i in range(self.n_c0): - for j in range(self.n_c1): + for i in range(self.n_c0): + for j in range(self.n_c1): if hx[i][j] == 1: incidence_01[j].append(i) - - for i in range(self.n_c2): - for j in range(self.n_c1): + for i in range(self.n_c2): + for j in range(self.n_c1): if hz[i][j] == 1: - incidence_02[i].append(j) + incidence_02[i].append(j) - for i in range(self.n_c1): CSS_cc.add_cell(incidence_01[i], rank=1) for i in range(self.n_c2): CSS_cc.add_cell(incidence_02[i], rank=2) - + return [ - torch.tensor(CSS_cc.coadjacency_matrix(1,0).todense()), - torch.tensor(CSS_cc.coadjacency_matrix(2,0).todense()), - torch.tensor(CSS_cc.adjacency_matrix(1,0).todense()), - torch.tensor(CSS_cc.coadjacency_matrix(2,1).todense()), - torch.tensor(CSS_cc.adjacency_matrix(2,0).todense()), - torch.tensor(CSS_cc.adjacency_matrix(2,1).todense()), - torch.tensor(CSS_cc.boundary_matrix(0,1).todense()), - torch.tensor(CSS_cc.boundary_matrix(0,2).todense()), - torch.tensor(CSS_cc.boundary_matrix(1,2).todense()) + torch.tensor(CSS_cc.coadjacency_matrix(1, 0).todense()), + torch.tensor(CSS_cc.coadjacency_matrix(2, 0).todense()), + torch.tensor(CSS_cc.adjacency_matrix(1, 0).todense()), + torch.tensor(CSS_cc.coadjacency_matrix(2, 1).todense()), + torch.tensor(CSS_cc.adjacency_matrix(2, 0).todense()), + torch.tensor(CSS_cc.adjacency_matrix(2, 1).todense()), + torch.tensor(CSS_cc.boundary_matrix(0, 1).todense()), + torch.tensor(CSS_cc.boundary_matrix(0, 2).todense()), + torch.tensor(CSS_cc.boundary_matrix(1, 2).todense()), ] -if __name__ == '__main__': - - Hx = [[1,1,1,0,0,0,1,0],[0,0,1,0,1,1,1,0],[1,1,0,1,0,0,0,1],[0,0,0,1,1,1,0,1]] - Hz = [[0,1,0,0,1,0,1,1],[0,1,1,1,1,0,0,0],[1,0,0,0,0,1,1,1],[1,0,1,1,0,1,0,0]] - encoder = CSSEncoder(8,4,4,mode='graph',grakel_use=True) - class c(): - def __init__(self,hx,hz): +if __name__ == "__main__": + Hx = [ + [1, 1, 1, 0, 0, 0, 1, 0], + [0, 0, 1, 0, 1, 1, 1, 0], + [1, 1, 0, 1, 0, 0, 0, 1], + [0, 0, 0, 1, 1, 1, 0, 1], + ] + Hz = [ + [0, 1, 0, 0, 1, 0, 1, 1], + [0, 1, 1, 1, 1, 0, 0, 0], + [1, 0, 0, 0, 0, 1, 1, 1], + [1, 0, 1, 1, 0, 1, 0, 0], + ] + encoder = CSSEncoder(8, 4, 4, mode="graph", grakel_use=True) + + class c: + def __init__(self, hx, hz): self.hx = hx - self.hz=hz - relations = encoder.encode(c(hx= Hx,hz= Hz)) + self.hz = hz + + relations = encoder.encode(c(hx=Hx, hz=Hz)) print(type(relations)) # print(relations.dictionary) # print(relations) # print(type(relations[0])) - diff --git a/bayesian_optimization/gadget.py b/bayesian_optimization/gadget.py index a2007e3..6dac515 100644 --- a/bayesian_optimization/gadget.py +++ b/bayesian_optimization/gadget.py @@ -11,36 +11,41 @@ try: import scipy.sparse as sp import scipy.sparse.linalg as spla + _HAS_SCIPY = True except Exception: _HAS_SCIPY = False class ChainComplexGlobalFeaturesEncoder: - - - def __init__(self, - return_torch: bool = True, - dtype: Optional["torch.dtype"] = None, - device: Optional["torch.device"] = None, - max_degree_bin: int = 10, - quantiles: Sequence[float] = (0.10, 0.25, 0.50, 0.75, 0.90), - # SVD options - spectral_backend: str = "auto", # "auto" | "scipy" | "power" - power_iters: int = 200, - power_tol: float = 1e-6, - power_seed: int = 0, - # two-step & NB options - eig_iters: int = 200, - eig_tol: float = 1e-6, - nb_iters: int = 200, - nb_tol: float = 1e-6, - nb_seed: int = 1234, - # Hodge options - hodge_k: int = 8, - hodge_dense_threshold: int = 2048): + def __init__( + self, + return_torch: bool = True, + dtype: Optional["torch.dtype"] = None, + device: Optional["torch.device"] = None, + max_degree_bin: int = 10, + quantiles: Sequence[float] = (0.10, 0.25, 0.50, 0.75, 0.90), + # SVD options + spectral_backend: str = "auto", # "auto" | "scipy" | "power" + power_iters: int = 200, + power_tol: float = 1e-6, + power_seed: int = 0, + # two-step & NB options + eig_iters: int = 200, + eig_tol: float = 1e-6, + nb_iters: int = 200, + nb_tol: float = 1e-6, + nb_seed: int = 1234, + # Hodge options + hodge_k: int = 8, + hodge_dense_threshold: int = 2048, + ): self.return_torch = return_torch and (torch is not None) - self.dtype = dtype if dtype is not None else (torch.float32 if self.return_torch else np.float32) + self.dtype = ( + dtype + if dtype is not None + else (torch.float32 if self.return_torch else np.float32) + ) self.device = device self.max_degree_bin = int(max_degree_bin) self.quantiles = tuple(float(q) for q in quantiles) @@ -65,7 +70,7 @@ def __init__(self, # ---------- degrees ---------- @staticmethod def _np_deg_arrays(h: np.ndarray): - Hnz = (h != 0) + Hnz = h != 0 var_deg = Hnz.sum(axis=0).astype(np.int64, copy=False).ravel() chk_deg = Hnz.sum(axis=1).astype(np.int64, copy=False).ravel() return var_deg, chk_deg @@ -99,25 +104,38 @@ def _sigma2_power(H: np.ndarray, iters: int, tol: float, seed: int) -> float: if m == 0 or n == 0: return 0.0 rng = np.random.default_rng(seed) - def AtA(v): return H.T @ (H @ v) - v = rng.standard_normal(n); v /= (np.linalg.norm(v) + 1e-12) + def AtA(v): + return H.T @ (H @ v) + + v = rng.standard_normal(n) + v /= np.linalg.norm(v) + 1e-12 lam_old = 0.0 for _ in range(iters): - w = AtA(v); nrm = np.linalg.norm(w) - if nrm < 1e-14: return 0.0 - v = w / nrm; lam = float(v @ AtA(v)) - if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): break + w = AtA(v) + nrm = np.linalg.norm(w) + if nrm < 1e-14: + return 0.0 + v = w / nrm + lam = float(v @ AtA(v)) + if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): + break lam_old = lam v1 = v.copy() - v = rng.standard_normal(n); v -= (v @ v1) * v1 - v /= (np.linalg.norm(v) + 1e-12) + v = rng.standard_normal(n) + v -= (v @ v1) * v1 + v /= np.linalg.norm(v) + 1e-12 lam_old = 0.0 for _ in range(iters): - w = AtA(v); w -= (w @ v1) * v1; nrm = np.linalg.norm(w) - if nrm < 1e-14: return 0.0 - v = w / nrm; lam = float(v @ AtA(v)) - if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): break + w = AtA(v) + w -= (w @ v1) * v1 + nrm = np.linalg.norm(w) + if nrm < 1e-14: + return 0.0 + v = w / nrm + lam = float(v @ AtA(v)) + if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): + break lam_old = lam return float(np.sqrt(max(lam, 0.0))) @@ -132,30 +150,40 @@ def _sigma2_scipy(H: np.ndarray) -> float: s = np.sort(np.array(s, dtype=np.float64)) return float(max(s[-2] if s.size >= 2 else 0.0, 0.0)) except Exception: - return ChainComplexGlobalFeaturesEncoder._sigma2_power(H, iters=200, tol=1e-6, seed=0) + return ChainComplexGlobalFeaturesEncoder._sigma2_power( + H, iters=200, tol=1e-6, seed=0 + ) def _sigma2_pair(self, Hx: np.ndarray, Hz: np.ndarray) -> Tuple[float, float]: Hx_bin = self._binarize_float(Hx) Hz_bin = self._binarize_float(Hz) - use_scipy = (_HAS_SCIPY and self.spectral_backend in ("auto", "scipy")) + use_scipy = _HAS_SCIPY and self.spectral_backend in ("auto", "scipy") if use_scipy: return self._sigma2_scipy(Hx_bin), self._sigma2_scipy(Hz_bin) - return (self._sigma2_power(Hx_bin, self.power_iters, self.power_tol, self.power_seed), - self._sigma2_power(Hz_bin, self.power_iters, self.power_tol, self.power_seed + 1)) + return ( + self._sigma2_power( + Hx_bin, self.power_iters, self.power_tol, self.power_seed + ), + self._sigma2_power( + Hz_bin, self.power_iters, self.power_tol, self.power_seed + 1 + ), + ) # ---------- lambda2 on variable–variable 2-step ---------- @staticmethod def _build_varvar_norm_adj_sparse(H: np.ndarray): Hs = sp.csr_matrix((H != 0).astype(np.int8)) A = Hs.T @ Hs - A.setdiag(0); A.eliminate_zeros() + A.setdiag(0) + A.eliminate_zeros() if A.shape[0] == 0: return sp.csr_matrix((0, 0)), np.array([], dtype=bool) deg = np.array(A.sum(axis=1)).ravel() mask = deg > 0 if mask.sum() == 0: return sp.csr_matrix((0, 0)), mask - A = A[mask][:, mask]; deg = deg[mask] + A = A[mask][:, mask] + deg = deg[mask] dinv = 1.0 / np.sqrt(deg) A = A.tocoo() data = A.data * dinv[A.row] * dinv[A.col] @@ -163,27 +191,41 @@ def _build_varvar_norm_adj_sparse(H: np.ndarray): return An, mask @staticmethod - def _eig2_symmetric_power_dense(An: np.ndarray, iters: int, tol: float, seed: int) -> float: + def _eig2_symmetric_power_dense( + An: np.ndarray, iters: int, tol: float, seed: int + ) -> float: n = An.shape[0] - if n == 0: return 0.0 + if n == 0: + return 0.0 rng = np.random.default_rng(seed) - v = rng.standard_normal(n); v /= (np.linalg.norm(v) + 1e-12) + v = rng.standard_normal(n) + v /= np.linalg.norm(v) + 1e-12 lam_old = 0.0 for _ in range(iters): - w = An @ v; nrm = np.linalg.norm(w) - if nrm < 1e-14: return 0.0 - v = w / nrm; lam = float(v @ (An @ v)) - if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): break + w = An @ v + nrm = np.linalg.norm(w) + if nrm < 1e-14: + return 0.0 + v = w / nrm + lam = float(v @ (An @ v)) + if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): + break lam_old = lam v1 = v.copy() - v = rng.standard_normal(n); v -= (v @ v1) * v1 - v /= (np.linalg.norm(v) + 1e-12) + v = rng.standard_normal(n) + v -= (v @ v1) * v1 + v /= np.linalg.norm(v) + 1e-12 lam_old = 0.0 for _ in range(iters): - w = An @ v; w -= (w @ v1) * v1; nrm = np.linalg.norm(w) - if nrm < 1e-14: return 0.0 - v = w / nrm; lam = float(v @ (An @ v)) - if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): break + w = An @ v + w -= (w @ v1) * v1 + nrm = np.linalg.norm(w) + if nrm < 1e-14: + return 0.0 + v = w / nrm + lam = float(v @ (An @ v)) + if abs(lam - lam_old) <= tol * max(1.0, abs(lam)): + break lam_old = lam return float(min(1.0, max(-1.0, lam))) @@ -196,28 +238,42 @@ def _lambda2_varvar(self, H: np.ndarray) -> float: try: vals = spla.eigsh(An, k=2, which="LM", return_eigenvectors=False) vals = np.sort(np.abs(vals)) - return float(min(1.0, max(0.0, vals[-2]))) if vals.size >= 2 else float(vals[-1]) + return ( + float(min(1.0, max(0.0, vals[-2]))) + if vals.size >= 2 + else float(vals[-1]) + ) except Exception: Ad = An.toarray() - return self._eig2_symmetric_power_dense(Ad, self.eig_iters, self.eig_tol, seed=42) + return self._eig2_symmetric_power_dense( + Ad, self.eig_iters, self.eig_tol, seed=42 + ) else: A = Hbin.T @ Hbin np.fill_diagonal(A, 0.0) deg = A.sum(axis=1) mask = deg > 0 - if not mask.any(): return 0.0 - A = A[np.ix_(mask, mask)]; deg = deg[mask] + if not mask.any(): + return 0.0 + A = A[np.ix_(mask, mask)] + deg = deg[mask] dinv = 1.0 / np.sqrt(deg) An = (A * dinv).T * dinv - return self._eig2_symmetric_power_dense(An, self.eig_iters, self.eig_tol, seed=42) + return self._eig2_symmetric_power_dense( + An, self.eig_iters, self.eig_tol, seed=42 + ) - def _lambda2_varvar_pair(self, Hx: np.ndarray, Hz: np.ndarray) -> Tuple[float, float]: + def _lambda2_varvar_pair( + self, Hx: np.ndarray, Hz: np.ndarray + ) -> Tuple[float, float]: return self._lambda2_varvar(Hx), self._lambda2_varvar(Hz) # ---------- non-backtracking spectral radius ---------- @staticmethod - def _nb_build_edge_lists(H: np.ndarray) -> Tuple[List[List[int]], List[List[int]], np.ndarray, np.ndarray]: - Hnz = (H != 0) + def _nb_build_edge_lists( + H: np.ndarray, + ) -> Tuple[List[List[int]], List[List[int]], np.ndarray, np.ndarray]: + Hnz = H != 0 m, n = Hnz.shape N_v = [list(np.where(Hnz[:, j])[0]) for j in range(n)] N_c = [list(np.where(Hnz[i, :])[0]) for i in range(m)] @@ -225,50 +281,69 @@ def _nb_build_edge_lists(H: np.ndarray) -> Tuple[List[List[int]], List[List[int] chk_idx = [] for j in range(n): for i in N_v[j]: - var_idx.append(j); chk_idx.append(i) - return N_v, N_c, np.asarray(var_idx, dtype=np.int64), np.asarray(chk_idx, dtype=np.int64) + var_idx.append(j) + chk_idx.append(i) + return ( + N_v, + N_c, + np.asarray(var_idx, dtype=np.int64), + np.asarray(chk_idx, dtype=np.int64), + ) def _nonbacktracking_rho(self, H: np.ndarray) -> float: N_v, N_c, var_idx, chk_idx = self._nb_build_edge_lists(H) E = var_idx.size - if E == 0: return 0.0 + if E == 0: + return 0.0 rng = np.random.default_rng(self.nb_seed) - a = rng.standard_normal(E); b = rng.standard_normal(E) + a = rng.standard_normal(E) + b = rng.standard_normal(E) edges_of_var = [[] for _ in range(len(N_v))] edges_of_chk = [[] for _ in range(len(N_c))] for e in range(E): - v = var_idx[e]; c = chk_idx[e] - edges_of_var[v].append(e); edges_of_chk[c].append(e) + v = var_idx[e] + c = chk_idx[e] + edges_of_var[v].append(e) + edges_of_chk[c].append(e) def step(a, b): sum_a_c = np.zeros(len(N_c)) for c, edges in enumerate(edges_of_chk): - if edges: sum_a_c[c] = a[edges].sum() + if edges: + sum_a_c[c] = a[edges].sum() new_b = sum_a_c[chk_idx] - a sum_b_v = np.zeros(len(N_v)) for v, edges in enumerate(edges_of_var): - if edges: sum_b_v[v] = new_b[edges].sum() + if edges: + sum_b_v[v] = new_b[edges].sum() new_a = sum_b_v[var_idx] - new_b return new_a, new_b prev_norm = np.linalg.norm(np.concatenate([a, b])) if prev_norm < 1e-12: - a[:] = 1.0; b[:] = 1.0; prev_norm = np.linalg.norm(np.concatenate([a, b])) + a[:] = 1.0 + b[:] = 1.0 + prev_norm = np.linalg.norm(np.concatenate([a, b])) rho = 0.0 for _ in range(self.nb_iters): new_a, new_b = step(a, b) y = np.concatenate([new_a, new_b]) y_norm = np.linalg.norm(y) - if y_norm < 1e-18: return 0.0 + if y_norm < 1e-18: + return 0.0 rho_new = y_norm / prev_norm a, b = new_a / y_norm, new_b / y_norm if abs(rho_new - rho) <= self.nb_tol * max(1.0, abs(rho_new)): - rho = rho_new; break - rho = rho_new; prev_norm = 1.0 + rho = rho_new + break + rho = rho_new + prev_norm = 1.0 return float(rho) - def _nonbacktracking_rho_pair(self, Hx: np.ndarray, Hz: np.ndarray) -> Tuple[float, float]: + def _nonbacktracking_rho_pair( + self, Hx: np.ndarray, Hz: np.ndarray + ) -> Tuple[float, float]: return self._nonbacktracking_rho(Hx), self._nonbacktracking_rho(Hz) # ---------- 4-cycle counts ---------- @@ -282,7 +357,8 @@ def _count_c4(H: np.ndarray) -> int: if _HAS_SCIPY: Hs = sp.csr_matrix(Hbin) B = (Hs.T @ Hs).tocsr() - B.setdiag(0); B.eliminate_zeros() + B.setdiag(0) + B.eliminate_zeros() # use only upper triangle Bu = sp.triu(B, k=1).tocoo() v = Bu.data.astype(np.int64, copy=False) @@ -303,18 +379,23 @@ def _count_c4_mixed(Hx: np.ndarray, Hz: np.ndarray) -> int: X = (Hx != 0).astype(np.int64, copy=False) Z = (Hz != 0).astype(np.int64, copy=False) if _HAS_SCIPY: - Xs = sp.csr_matrix(X); Zs = sp.csr_matrix(Z) - BX = (Xs.T @ Xs).tocsr(); BZ = (Zs.T @ Zs).tocsr() + Xs = sp.csr_matrix(X) + Zs = sp.csr_matrix(Z) + BX = (Xs.T @ Xs).tocsr() + BZ = (Zs.T @ Zs).tocsr() for M in (BX, BZ): - M.setdiag(0); M.eliminate_zeros() + M.setdiag(0) + M.eliminate_zeros() # upper triangle product BXu = sp.triu(BX, k=1).tocoo() # Pull corresponding entries from BZ (sparse gather) vals = BZ.tocsr()[BXu.row, BXu.col].A.ravel().astype(np.int64, copy=False) return int(np.sum(BXu.data.astype(np.int64) * vals)) else: - BX = X.T @ X; BZ = Z.T @ Z - np.fill_diagonal(BX, 0); np.fill_diagonal(BZ, 0) + BX = X.T @ X + BZ = Z.T @ Z + np.fill_diagonal(BX, 0) + np.fill_diagonal(BZ, 0) iu = np.triu_indices(BX.shape[0], k=1) return int(np.sum(BX[iu] * BZ[iu])) @@ -335,8 +416,9 @@ def _hodge_L1_small_eigs(self, Hx: np.ndarray, Hz: np.ndarray) -> np.ndarray: k = min(self.hodge_k, max(1, n - 1)) # eigsh requires k < n if _HAS_SCIPY: - Xs = sp.csr_matrix(HxF); Zs = sp.csr_matrix(HzF) - L1 = (Xs.T @ Xs) + (Zs.T @ Zs) # n x n sparse, PSD + Xs = sp.csr_matrix(HxF) + Zs = sp.csr_matrix(HzF) + L1 = (Xs.T @ Xs) + (Zs.T @ Zs) # n x n sparse, PSD try: vals = spla.eigsh(L1, k=k, which="SA", return_eigenvectors=False) vals = np.sort(np.maximum(vals, 0.0)) @@ -354,7 +436,9 @@ def _hodge_L1_small_eigs(self, Hx: np.ndarray, Hz: np.ndarray) -> np.ndarray: else: # crude randomized subspace iteration for smallest (shift by epsilon) eps = 1e-6 - M = np.linalg.pinv(L1 + eps * np.eye(n)) # inverse approximates smallest of L1 + M = np.linalg.pinv( + L1 + eps * np.eye(n) + ) # inverse approximates smallest of L1 # now largest eigenvalues of M ≈ 1/(smallest of L1) r = min(k, 8) V = np.random.default_rng(0).standard_normal((n, r)) @@ -366,7 +450,7 @@ def _hodge_L1_small_eigs(self, Hx: np.ndarray, Hz: np.ndarray) -> np.ndarray: # pad / cast out = np.zeros(self.hodge_k, dtype=np.float32) - out[:len(vals)] = vals.astype(np.float32) + out[: len(vals)] = vals.astype(np.float32) return out # ---------- main encode ---------- @@ -375,7 +459,8 @@ def encode(self, c) -> "np.ndarray | torch.Tensor": slices = {} # Base - n = int(c.n); k = int(c.k) + n = int(c.n) + k = int(c.k) rank_hx = int(c.gf2_rank(c.hx)) rank_hz = int(c.gf2_rank(c.hz)) base = np.array([n, k, rank_hx, rank_hz], dtype=np.float32) @@ -394,38 +479,47 @@ def encode(self, c) -> "np.ndarray | torch.Tensor": ("cZ_hist", self._hist_node_perspective(cz)), ("vM_hist", self._hist_node_perspective(vmerge)), ]: - slices[key] = (len(feats), len(feats) + arr.size); feats.append(arr) + slices[key] = (len(feats), len(feats) + arr.size) + feats.append(arr) for key, arr in [ - ("vX_q", self._quantiles(vx)), ("cX_q", self._quantiles(cx)), - ("vZ_q", self._quantiles(vz)), ("cZ_q", self._quantiles(cz)), + ("vX_q", self._quantiles(vx)), + ("cX_q", self._quantiles(cx)), + ("vZ_q", self._quantiles(vz)), + ("cZ_q", self._quantiles(cz)), ("vM_q", self._quantiles(vmerge)), ]: - slices[key] = (len(feats), len(feats) + arr.size); feats.append(arr) + slices[key] = (len(feats), len(feats) + arr.size) + feats.append(arr) # Spectral: sigma2 s2x, s2z = self._sigma2_pair(c.hx, c.hz) spec_sigma2 = np.array([s2x, s2z], dtype=np.float32) - slices["spec_sigma2"] = (len(feats), len(feats) + spec_sigma2.size); feats.append(spec_sigma2) + slices["spec_sigma2"] = (len(feats), len(feats) + spec_sigma2.size) + feats.append(spec_sigma2) # Spectral: lambda2 (var–var) l2x, l2z = self._lambda2_varvar_pair(c.hx, c.hz) spec_l2vv = np.array([l2x, l2z], dtype=np.float32) - slices["spec_lambda2_vv"] = (len(feats), len(feats) + spec_l2vv.size); feats.append(spec_l2vv) + slices["spec_lambda2_vv"] = (len(feats), len(feats) + spec_l2vv.size) + feats.append(spec_l2vv) # Spectral: non-backtracking rho rho_x, rho_z = self._nonbacktracking_rho_pair(c.hx, c.hz) spec_nb = np.array([rho_x, rho_z], dtype=np.float32) - slices["spec_nb_rho"] = (len(feats), len(feats) + spec_nb.size); feats.append(spec_nb) + slices["spec_nb_rho"] = (len(feats), len(feats) + spec_nb.size) + feats.append(spec_nb) # 4-cycles: [X, Z, mixed] c4x, c4z, c4m = self._count_c4_all(c.hx, c.hz) c4 = np.array([c4x, c4z, c4m], dtype=np.float32) - slices["cycles_4"] = (len(feats), len(feats) + c4.size); feats.append(c4) + slices["cycles_4"] = (len(feats), len(feats) + c4.size) + feats.append(c4) # Hodge / L1 smallest eigenvalues l1_small = self._hodge_L1_small_eigs(c.hx, c.hz) - slices["hodge_L1_small"] = (len(feats), len(feats) + l1_small.size); feats.append(l1_small) + slices["hodge_L1_small"] = (len(feats), len(feats) + l1_small.size) + feats.append(l1_small) vec = np.concatenate(feats, dtype=np.float32) self.last_slices = slices diff --git a/bayesian_optimization/gp.py b/bayesian_optimization/gp.py index d10fa71..0401d65 100644 --- a/bayesian_optimization/gp.py +++ b/bayesian_optimization/gp.py @@ -1,6 +1,7 @@ """ - This file defines the Gaussian Process model used in Bayesian Optimization +This file defines the Gaussian Process model used in Bayesian Optimization """ + from typing import Optional import torch import torch.nn as nn @@ -15,82 +16,101 @@ from gpytorch.priors import LogNormalPrior from gpytorch.constraints import Interval import math -device='cuda' + +device = "cuda" class NNMean(Mean): def __init__(self, input_size, hidden=256): super().__init__() self.net = nn.Sequential( - nn.Linear(input_size, hidden), - nn.LeakyReLU(0.1), - nn.Linear(hidden, 1) + nn.Linear(input_size, hidden), nn.LeakyReLU(0.1), nn.Linear(hidden, 1) ) + def forward(self, x): return self.net(x).squeeze(-1) - -class GaussianProcess_QEC_(ExactGP, GPyTorchModel): +class GaussianProcess_QEC_(ExactGP, GPyTorchModel): _num_outputs = 1 # to inform GPyTorchModel API - def __init__(self, train_X, train_Y, kernel=None,encoder = None,embedding = None,train_Yvar: Optional[Tensor] = None,mean = 'linear',grakel=False,mean_input = 16,encoder2 = None,mean_module=None): + def __init__( + self, + train_X, + train_Y, + kernel=None, + encoder=None, + embedding=None, + train_Yvar: Optional[Tensor] = None, + mean="linear", + grakel=False, + mean_input=16, + encoder2=None, + mean_module=None, + ): # NOTE: This ignores train_Yvar and uses inferred noise instead. # squeeze output dim before passing train_Y to ExactGP - likelihood = likelihood = GaussianLikelihood( - noise_prior=LogNormalPrior(math.log(1e-2), 0.5), - noise_constraint=Interval(1e-6, 1.0) + likelihood = likelihood = GaussianLikelihood( + noise_prior=LogNormalPrior(math.log(1e-2), 0.5), + noise_constraint=Interval(1e-6, 1.0), ) - super().__init__(train_X, train_Y.squeeze(-1),likelihood) + super().__init__(train_X, train_Y.squeeze(-1), likelihood) self.grakel = grakel - if encoder==None: + if encoder == None: + def encode_(x): return x + self.encode = encode_ else: self.encode = encoder - if encoder2==None: + if encoder2 == None: + def encode_(x): return x + self.encode2 = encode_ else: self.encode2 = encoder2 - if embedding==None: + if embedding == None: + def embed_(x): return x + self.embed = embed_ else: self.embed = embedding - if mean in ['linear']: + if mean in ["linear"]: self.mean_module = LinearMean(mean_input) - elif mean in ['nn','NN']: + elif mean in ["nn", "NN"]: self.mean_module = NNMean(input_size=mean_input, hidden=64) - elif mean in ['given']: + elif mean in ["given"]: self.mean_module = mean_module - else : + else: self.mean_module = ConstantMean() self.covar_module = kernel - self.to(train_X) + self.to(train_X) def forward(self, x): - + # print(type(feats),len(feats)) # print(feats) # print('end printing') # print("feats mean/std:", feats.mean().item(), feats.std().item()) - if self.grakel==True: - mean_x = self.mean_module(self.encode2(x)).squeeze(-1) + if self.grakel == True: + mean_x = self.mean_module(self.encode2(x)).squeeze(-1) feats = self.embed(self.encode(x)) - else: + else: feats = self.embed(self.encode(x)) - mean_x = self.mean_module(feats) + mean_x = self.mean_module(feats) covar_x = self.covar_module(feats) - + # print(f'covar_x:{type(covar_x)},{covar_x}') - + return MultivariateNormal(mean_x, covar_x) + class GaussianProcess_QEC(gpytorch.models.ExactGP): """ ExactGP that: @@ -99,31 +119,41 @@ class GaussianProcess_QEC(gpytorch.models.ExactGP): --> GP mean(z), cov(z) """ - def __init__(self, train_x, train_y, - *, - likelihood: gpytorch.likelihoods.GaussianLikelihood, - kernel: gpytorch.kernels.Kernel, - encoder, # callable: encode(X_batch) -> list of views - embed: nn.Module, # ChainComplexEmbedder - mean: str = 'constant', - mean_input: int = 64): + def __init__( + self, + train_x, + train_y, + *, + likelihood: gpytorch.likelihoods.GaussianLikelihood, + kernel: gpytorch.kernels.Kernel, + encoder, # callable: encode(X_batch) -> list of views + embed: nn.Module, # ChainComplexEmbedder + mean: str = "constant", + mean_input: int = 64, + ): # train_y must be 1D for ExactGP; we'll squeeze in caller - super().__init__(train_x, train_y.squeeze(-1) if train_y.dim() == 2 else train_y, likelihood) - self.encoder = encoder # Python callable (non-differentiable wrt X) - self.embed = embed # nn.Module on device + super().__init__( + train_x, train_y.squeeze(-1) if train_y.dim() == 2 else train_y, likelihood + ) + self.encoder = encoder # Python callable (non-differentiable wrt X) + self.embed = embed # nn.Module on device self.embed.to(device) - d_model = next(self.embed.parameters()).shape[-1] if hasattr(self.embed, 'parameters') else 128 - d_model = getattr(self.embed, 'd_model', d_model) + d_model = ( + next(self.embed.parameters()).shape[-1] + if hasattr(self.embed, "parameters") + else 128 + ) + d_model = getattr(self.embed, "d_model", d_model) # Mean module - if mean == 'zero': + if mean == "zero": self.mean_module = gpytorch.means.ZeroMean() - elif mean == 'constant': + elif mean == "constant": self.mean_module = gpytorch.means.ConstantMean() - elif mean == 'linear': + elif mean == "linear": self.mean_module = gpytorch.means.LinearMean(input_size=d_model) - elif mean == 'nn': + elif mean == "nn": # Small MLP mean head on top of z self.nn_mean = nn.Sequential( nn.LayerNorm(d_model), @@ -131,12 +161,16 @@ def __init__(self, train_x, train_y, nn.GELU(), nn.Linear(mean_input, 1), ).to(device) + # wrap to a Mean that calls nn_mean (simple lambda-style) class _NNMean(gpytorch.means.Mean): - def __init__(self, head): - super().__init__(); self.head = head - def forward(self, x): # x: (N, d_model) + def __init__(self, head): + super().__init__() + self.head = head + + def forward(self, x): # x: (N, d_model) return self.head(x).squeeze(-1) + self.mean_module = _NNMean(self.nn_mean) else: self.mean_module = gpytorch.means.ConstantMean() @@ -153,10 +187,12 @@ def _x_to_z(self, X: torch.Tensor) -> torch.Tensor: Convert a batch of design vectors X (B, D) to embedding z (B, d_model). """ # Move to CPU numpy for code constructor if needed - X_np = X.detach().cpu().numpy() if isinstance(X, torch.Tensor) else np.asarray(X) - sample_views_list = self.encoder(X_np) # list of views (len=B) + X_np = ( + X.detach().cpu().numpy() if isinstance(X, torch.Tensor) else np.asarray(X) + ) + sample_views_list = self.encoder(X_np) # list of views (len=B) # embedder.forward(list) packs internally and returns (B, d_model) - z = self.embed(sample_views_list) # (B, d_model) on device + z = self.embed(sample_views_list) # (B, d_model) on device return z def forward(self, X: torch.Tensor) -> MultivariateNormal: @@ -164,23 +200,25 @@ def forward(self, X: torch.Tensor) -> MultivariateNormal: Return latent f(X) distribution (MVN). GPyTorch's likelihood wraps this to p(y|X). """ X = X.to(device) - z = self._x_to_z(X) # (N, d_model) + z = self._x_to_z(X) # (N, d_model) # cache self._cache_last_inputs = X self._cache_last_z = z - mean_x = self.mean_module(z) # (N,) - covar_x = self.covar_module(z) # kernel on z + mean_x = self.mean_module(z) # (N,) + covar_x = self.covar_module(z) # kernel on z return MultivariateNormal(mean_x, covar_x) # 若你更喜欢 model.posterior(X) 的接口,可以保留: def posterior(self, X: torch.Tensor): - self.eval(); self.likelihood.eval() + self.eval() + self.likelihood.eval() with torch.no_grad(), gpytorch.settings.fast_pred_var(): # predictive distribution of y return self.likelihood(self(X.to(device))) - -if __name__ == '__main__': + + +if __name__ == "__main__": import torch import numpy as np import gpytorch @@ -190,6 +228,7 @@ def posterior(self, X: torch.Tensor): from gpytorch.likelihoods import GaussianLikelihood from gpytorch.mlls import ExactMarginalLogLikelihood from torch.optim import Adam + def train_gpytorch_model(model, train_x, train_y, training_iter=100, lr=0.1): model.train() model.likelihood.train() @@ -203,6 +242,7 @@ def train_gpytorch_model(model, train_x, train_y, training_iter=100, lr=0.1): loss.backward() optimizer.step() return model + n_train = 20 train_x = torch.linspace(0, 1, n_train).unsqueeze(-1) train_y = torch.sin(train_x.squeeze() * 2 * np.pi) + 0.2 * torch.randn(n_train) @@ -232,4 +272,4 @@ def train_gpytorch_model(model, train_x, train_y, training_iter=100, lr=0.1): # 4. Print first few predictions print("First 10 mean predictions:\n", mean[:10].numpy()) - print("First 10 stddev predictions:\n", stddev[:10].numpy()) \ No newline at end of file + print("First 10 stddev predictions:\n", stddev[:10].numpy()) diff --git a/bayesian_optimization/graphembedding.py b/bayesian_optimization/graphembedding.py index ec58ae1..2720ff7 100644 --- a/bayesian_optimization/graphembedding.py +++ b/bayesian_optimization/graphembedding.py @@ -1,7 +1,7 @@ import torch from torch import nn import torch.nn.functional as F -from torch_geometric.nn import GCNConv,global_mean_pool +from torch_geometric.nn import GCNConv, global_mean_pool from torch_geometric.utils import dense_to_sparse from torch_geometric.data import Data, Batch from torch_geometric.nn import TransformerConv @@ -9,56 +9,65 @@ class GraphRegressor(nn.Module): - def __init__(self,n,nx,nz,hidden_channel = 128, embedding_mode='GIN'): + def __init__(self, n, nx, nz, hidden_channel=128, embedding_mode="GIN"): super().__init__() self.embedding_mode = embedding_mode - if self.embedding_mode in ['GIN']: - self.embedder = GINEmbedder(n,nx,nz,hidden_channel) - elif self.embedding_mode in ['GT']: + if self.embedding_mode in ["GIN"]: + self.embedder = GINEmbedder(n, nx, nz, hidden_channel) + elif self.embedding_mode in ["GT"]: self.embedder = GraphTransformerEmbedder(n, nx, nz, hidden_dim=128) - elif self.embedding_mode in ['GCN']: - self.embedder = GCNEmbedder(n,nx,nz,hidden_channel) + elif self.embedding_mode in ["GCN"]: + self.embedder = GCNEmbedder(n, nx, nz, hidden_channel) self.fc = nn.Sequential( nn.Linear(hidden_channel, 256), - nn.BatchNorm1d(256), + nn.BatchNorm1d(256), nn.LeakyReLU(0.1), nn.Linear(256, 512), - nn.BatchNorm1d(512), + nn.BatchNorm1d(512), nn.LeakyReLU(0.1), - nn.Linear(512,1) - ) - def forward(self,x): + nn.Linear(512, 1), + ) + + def forward(self, x): embedding = self.embedder(x) return self.fc(embedding) + + class GraphEmbedderforGP(nn.Module): - def __init__(self,n,nx,nz,hidden_channel = 128, embedding_mode='GIN'): + def __init__(self, n, nx, nz, hidden_channel=128, embedding_mode="GIN"): super().__init__() self.embedding_mode = embedding_mode - if self.embedding_mode in ['GIN']: - self.embedder = GINEmbedder(n,nx,nz,hidden_channel) - elif self.embedding_mode in ['GT']: - self.embedder = GraphTransformerEmbedder(n, nx, nz, hidden_dim=hidden_channel) - elif self.embedding_mode in ['GCN']: - self.embedder = GCNEmbedder(n,nx,nz,hidden_channel) + if self.embedding_mode in ["GIN"]: + self.embedder = GINEmbedder(n, nx, nz, hidden_channel) + elif self.embedding_mode in ["GT"]: + self.embedder = GraphTransformerEmbedder( + n, nx, nz, hidden_dim=hidden_channel + ) + elif self.embedding_mode in ["GCN"]: + self.embedder = GCNEmbedder(n, nx, nz, hidden_channel) self.fc = nn.Sequential( nn.Linear(hidden_channel, 64), nn.LeakyReLU(0.1), nn.Linear(64, 128), nn.LeakyReLU(0.1), nn.Linear(128, 64), - ) - def forward(self,x): + ) + + def forward(self, x): embedding = self.embedder(x) return self.fc(embedding) - + class GraphEmbedder(nn.Module): def __init__(self, n, nx, nz): super().__init__() - q = torch.zeros(n, 3); q[:, 0] = 1.0 - x = torch.zeros(nx, 3); x[:, 1] = 1.0 - z = torch.zeros(nz, 3); z[:, 2] = 1.0 - self.register_buffer('single_nodes', torch.cat((q, x, z), dim=0)) + q = torch.zeros(n, 3) + q[:, 0] = 1.0 + x = torch.zeros(nx, 3) + x[:, 1] = 1.0 + z = torch.zeros(nz, 3) + z[:, 2] = 1.0 + self.register_buffer("single_nodes", torch.cat((q, x, z), dim=0)) def batch_transform(self, adjacency_matrices): batch_size = adjacency_matrices.size(0) @@ -70,18 +79,19 @@ def batch_transform(self, adjacency_matrices): return Batch.from_data_list(data_list).to(adjacency_matrices.device) - from torch import nn import torch.nn as nn + + class GCNEmbedder_WithLogicalOperators(nn.Module): - def __init__(self,hidden_dim,num_layers): + def __init__(self, hidden_dim, num_layers): super().__init__() # 1) normalize the raw 3-dim node features self.input_norm = nn.BatchNorm1d(5) # GCN layers and batch norms self.layers = nn.ModuleList() - self.bns = nn.ModuleList() + self.bns = nn.ModuleList() for i in range(num_layers): in_dim = 5 if i == 0 else hidden_dim self.layers.append(GCNConv(in_dim, hidden_dim)) @@ -91,8 +101,9 @@ def __init__(self,hidden_dim,num_layers): self.post_pool_norm = nn.LayerNorm(hidden_dim) self.num_layers = num_layers - def forward(self,batch): - + + def forward(self, batch): + x, edge_index = batch.x, batch.edge_index # --- input normalization --- @@ -112,7 +123,8 @@ def forward(self,batch): # --- embedding normalization --- out = self.post_pool_norm(out) return out # [batch_size, hidden_dim] - + + class GCNEmbedder(GraphEmbedder): def __init__(self, n, nx, nz, hidden_dim=128, num_layers=3): super().__init__(n, nx, nz) @@ -121,7 +133,7 @@ def __init__(self, n, nx, nz, hidden_dim=128, num_layers=3): # GCN layers and batch norms self.layers = nn.ModuleList() - self.bns = nn.ModuleList() + self.bns = nn.ModuleList() for i in range(num_layers): in_dim = 3 if i == 0 else hidden_dim self.layers.append(GCNConv(in_dim, hidden_dim)) @@ -155,6 +167,7 @@ def forward(self, adjacency_matrices): out = self.post_pool_norm(out) return out # [batch_size, hidden_dim] + class GINEmbedder(GraphEmbedder): def __init__(self, n, nx, nz, hidden_dim=128, num_layers=3): super().__init__(n, nx, nz) @@ -162,14 +175,14 @@ def __init__(self, n, nx, nz, hidden_dim=128, num_layers=3): self.input_norm = nn.BatchNorm1d(3) self.layers = nn.ModuleList() - self.bns = nn.ModuleList() + self.bns = nn.ModuleList() for i in range(num_layers): in_dim = 3 if i == 0 else hidden_dim mlp = nn.Sequential( nn.Linear(in_dim, hidden_dim), # ReLU for first layer, LeakyReLU thereafter nn.ReLU() if i == 0 else nn.LeakyReLU(0.1), - nn.Linear(hidden_dim, hidden_dim) + nn.Linear(hidden_dim, hidden_dim), ) self.layers.append(GINConv(mlp)) # you could swap to GraphNorm if you want normalization per-graph: @@ -203,19 +216,15 @@ def forward(self, adjacency_matrices): out = self.post_pool_norm(out) return out # [batch_size, hidden_dim] - - class GraphTransformerEmbedder(GraphEmbedder): - def __init__(self, n, nx, nz, - hidden_dim=128, num_layers=3, - heads=4, dropout=0.1): + def __init__(self, n, nx, nz, hidden_dim=128, num_layers=3, heads=4, dropout=0.1): super().__init__(n, nx, nz) - self.hidden_dim = hidden_dim - self.num_layers = num_layers - self.dropout = dropout - self.heads = heads + self.hidden_dim = hidden_dim + self.num_layers = num_layers + self.dropout = dropout + self.heads = heads # ——— Project input features (3 dims) up to hidden_dim ——— in_dim = self.single_nodes.size(1) @@ -225,35 +234,34 @@ def __init__(self, n, nx, nz, self.convs = nn.ModuleList() # first layer: hidden_dim → hidden_dim self.convs.append( - TransformerConv(hidden_dim, - hidden_dim // heads, - heads=heads, - dropout=dropout) + TransformerConv( + hidden_dim, hidden_dim // heads, heads=heads, dropout=dropout + ) ) # remaining layers for _ in range(num_layers - 1): self.convs.append( - TransformerConv(hidden_dim, - hidden_dim // heads, - heads=heads, - dropout=dropout) + TransformerConv( + hidden_dim, hidden_dim // heads, heads=heads, dropout=dropout + ) ) # ——— per-layer feed-forward + LayerNorm ——— - self.ffn = nn.ModuleList([ - nn.Sequential( - nn.Linear(hidden_dim, hidden_dim), - nn.ReLU(), - nn.Dropout(dropout) - ) for _ in range(num_layers) - ]) - self.layer_norms = nn.ModuleList([ - nn.LayerNorm(hidden_dim) for _ in range(num_layers) - ]) + self.ffn = nn.ModuleList( + [ + nn.Sequential( + nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Dropout(dropout) + ) + for _ in range(num_layers) + ] + ) + self.layer_norms = nn.ModuleList( + [nn.LayerNorm(hidden_dim) for _ in range(num_layers)] + ) def forward(self, adjacency_matrices): # 1) batchify - batch = self.batch_transform(adjacency_matrices) + batch = self.batch_transform(adjacency_matrices) x, edge_ix = batch.x, batch.edge_index # 2) initial projection @@ -262,66 +270,64 @@ def forward(self, adjacency_matrices): # 3) stacked Transformer layers with residuals for i, conv in enumerate(self.convs): h_res = h - h = conv(h, edge_ix) - h = F.dropout(h, p=self.dropout, training=self.training) - h = h + self.ffn[i](h) # FFN residual - h = h + h_res # skip residual - h = self.layer_norms[i](h) - h = F.relu(h) + h = conv(h, edge_ix) + h = F.dropout(h, p=self.dropout, training=self.training) + h = h + self.ffn[i](h) # FFN residual + h = h + h_res # skip residual + h = self.layer_norms[i](h) + h = F.relu(h) # 4) global readout out = global_mean_pool(h, batch.batch) return out # [batch_size, hidden_dim] + + import torch.nn as nn from torch_geometric.nn import GATv2Conv, global_max_pool, global_add_pool + class eGraphTransformerEmbedder(GraphEmbedder): - def __init__(self, n, nx, nz, - hidden_dim=512, num_layers=6, - heads=8, dropout=0.1): + def __init__(self, n, nx, nz, hidden_dim=512, num_layers=6, heads=8, dropout=0.1): super().__init__(n, nx, nz) - self.hidden_dim = hidden_dim - self.num_layers = num_layers - self.dropout = dropout - self.heads = heads - + self.hidden_dim = hidden_dim + self.num_layers = num_layers + self.dropout = dropout + self.heads = heads - self.single_nodes = torch.randn(n, nx) + self.single_nodes = torch.randn(n, nx) in_dim = self.single_nodes.size(1) - self.input_proj = nn.Linear(in_dim, hidden_dim) + self.convs = nn.ModuleList( + [ + GATv2Conv(hidden_dim, hidden_dim // heads, heads=heads, dropout=dropout) + for _ in range(num_layers) + ] + ) - self.convs = nn.ModuleList([ - GATv2Conv(hidden_dim, hidden_dim // heads, heads=heads, dropout=dropout) - for _ in range(num_layers) - ]) - - - self.ffn = nn.ModuleList([ - nn.Sequential( - nn.Linear(hidden_dim, hidden_dim * 2), - nn.ReLU(), - nn.Linear(hidden_dim * 2, hidden_dim), - nn.ReLU(), - nn.Dropout(dropout) - ) - for _ in range(num_layers) - ]) - self.layer_norms = nn.ModuleList([ - nn.LayerNorm(hidden_dim) for _ in range(num_layers) - ]) + self.ffn = nn.ModuleList( + [ + nn.Sequential( + nn.Linear(hidden_dim, hidden_dim * 2), + nn.ReLU(), + nn.Linear(hidden_dim * 2, hidden_dim), + nn.ReLU(), + nn.Dropout(dropout), + ) + for _ in range(num_layers) + ] + ) + self.layer_norms = nn.ModuleList( + [nn.LayerNorm(hidden_dim) for _ in range(num_layers)] + ) # 全局池化后拼接 -> 最终投影 self.global_proj = nn.Sequential( - nn.Linear(hidden_dim * 3, hidden_dim), - nn.ReLU(), - nn.Dropout(dropout) + nn.Linear(hidden_dim * 3, hidden_dim), nn.ReLU(), nn.Dropout(dropout) ) - def forward(self, adjacency_matrices): batch = self.batch_transform(adjacency_matrices) x, edge_ix = batch.x, batch.edge_index @@ -329,17 +335,16 @@ def forward(self, adjacency_matrices): for i in range(self.num_layers): h_res = h - h = self.convs[i](h, edge_ix) - h = F.dropout(h, p=self.dropout, training=self.training) - h = h + self.ffn[i](h) # FFN residual - h = h + h_res # skip residual - h = self.layer_norms[i](h) - h = F.relu(h) - + h = self.convs[i](h, edge_ix) + h = F.dropout(h, p=self.dropout, training=self.training) + h = h + self.ffn[i](h) # FFN residual + h = h + h_res # skip residual + h = self.layer_norms[i](h) + h = F.relu(h) mean_pool = global_mean_pool(h, batch.batch) - max_pool = global_max_pool(h, batch.batch) - sum_pool = global_add_pool(h, batch.batch) + max_pool = global_max_pool(h, batch.batch) + sum_pool = global_add_pool(h, batch.batch) pooled = torch.cat([mean_pool, max_pool, sum_pool], dim=-1) out = self.global_proj(pooled) # shape: [batch_size, hidden_dim] diff --git a/bayesian_optimization/kernels.py b/bayesian_optimization/kernels.py index 30f8788..e50ef43 100644 --- a/bayesian_optimization/kernels.py +++ b/bayesian_optimization/kernels.py @@ -1,14 +1,14 @@ """ - This file defines the kernels used in the Bayesian Optimization algorithm +This file defines the kernels used in the Bayesian Optimization algorithm """ -from gpytorch.kernels import Kernel,RBFKernel,ScaleKernel +from gpytorch.kernels import Kernel, RBFKernel, ScaleKernel import torch from torch import nn import torch.nn.functional as F from collections import defaultdict, Counter -from torch_geometric.nn import GCNConv,global_mean_pool +from torch_geometric.nn import GCNConv, global_mean_pool from topomodelx.nn.combinatorial.hmc import HMC from torch_geometric.utils import dense_to_sparse from torch_geometric.data import Data, Batch @@ -16,8 +16,9 @@ class GNNEmbedding(nn.Module): """ - Graph Neural Network based message passing layer (returns a cochain) for the CSS kernel + Graph Neural Network based message passing layer (returns a cochain) for the CSS kernel """ + def __init__(self, n, nx, nz, out_channels=128): super().__init__() @@ -29,7 +30,6 @@ def __init__(self, n, nx, nz, out_channels=128): z = torch.zeros(nz, 3) z[:, 2] = 1.0 self.single_nodes = torch.cat((q, x, z), dim=0) - # Graph convolutional layers self.conv1 = GCNConv(3, 128) @@ -81,71 +81,77 @@ def forward(self, adjacency_matrices): x = F.normalize(x, p=2, dim=-1) return x + + class GNNRegressor(nn.Module): def __init__(self, n, nx, nz, out_channels=128): super().__init__() self.embedding = GNNEmbedding(n, nx, nz, out_channels) self.fc = torch.nn.Linear(out_channels // 8, 1) - def forward(self,adjacency_matrix): + + def forward(self, adjacency_matrix): embedding = self.embedding(adjacency_matrix) out = self.fc(F.relu(embedding)) return out + + class GNNKernel(Kernel): - def __init__(self,n,nx,nz,**kwargs): + def __init__(self, n, nx, nz, **kwargs): super().__init__(**kwargs) - self.gnn = GNNEmbedding(n,nx,nz) + self.gnn = GNNEmbedding(n, nx, nz) self.rbf = RBFKernel() self.scale = ScaleKernel(self.rbf) - def forward(self, x1, x2,diag=False,**params): + + def forward(self, x1, x2, diag=False, **params): # x,y = self.gnn(adjacency_matrix1, adjacency_matrix2) x = self.gnn(x1) y = self.gnn(x2) # print(x,y) - return self.scale(x,y,diag=diag,**params) + return self.scale(x, y, diag=diag, **params) - -class WLSubtreeEmbedding(): - def __init__(self, n,nx,nz,num_iterations=3, **kwargs): + +class WLSubtreeEmbedding: + def __init__(self, n, nx, nz, num_iterations=3, **kwargs): super().__init__(**kwargs) self.num_iterations = num_iterations - q = torch.zeros(n,1) - x = torch.ones(nx,1) - z = -torch.ones(nz,1) - self.nodes = torch.cat((q,x,z),dim=0) + q = torch.zeros(n, 1) + x = torch.ones(nx, 1) + z = -torch.ones(nz, 1) + self.nodes = torch.cat((q, x, z), dim=0) + def forward(self, edge_index1, edge_index2): labels1 = self.apply_wl(edge_index1, self.num_iterations) labels2 = self.apply_wl(edge_index2, self.num_iterations) # print( labels1, labels2) # Compute kernel between these two sets of labels - v1,v2 = self.compute_hist(labels1, labels2) + v1, v2 = self.compute_hist(labels1, labels2) - - return v1,v2 + return v1, v2 def apply_wl(self, edge_index, num_iterations): neighbors = defaultdict(list) for i, j in edge_index.t().tolist(): # undirected graph neighbors[i].append(j) - neighbors[j].append(i) - + neighbors[j].append(i) - labels = self.nodes.clone() + labels = self.nodes.clone() for _ in range(num_iterations): - new_labels = labels.clone() + new_labels = labels.clone() for node, node_neighbors in neighbors.items(): + neighborhood_labels = [labels[node]] + [ + labels[n] for n in node_neighbors + ] - neighborhood_labels = [labels[node]] + [labels[n] for n in node_neighbors] - sorted_labels = sorted(neighborhood_labels) - new_label_str = '_'.join(str(lbl) for lbl in sorted_labels) + new_label_str = "_".join(str(lbl) for lbl in sorted_labels) new_labels[node] = hash(new_label_str) - - labels = new_labels - + + labels = new_labels + return labels def compute_hist(self, labels1, labels2): @@ -154,32 +160,40 @@ def compute_hist(self, labels1, labels2): hist1 = Counter(labels1.flatten().tolist()) hist2 = Counter(labels2.flatten().tolist()) all_labels = list(set(list(hist1.keys()) + list(hist2.keys()))) - vec1 = torch.tensor([hist1.get(label, 0) for label in all_labels], dtype=torch.float32) - vec2 = torch.tensor([hist2.get(label, 0) for label in all_labels], dtype=torch.float32) - + vec1 = torch.tensor( + [hist1.get(label, 0) for label in all_labels], dtype=torch.float32 + ) + vec2 = torch.tensor( + [hist2.get(label, 0) for label in all_labels], dtype=torch.float32 + ) + return vec1, vec2 + + class WLSubtreeKernel(Kernel): - def __init__(self, n,nx,nz,num_iterations=3,encoder = None,eps = 1e-8,**kwargs): + def __init__(self, n, nx, nz, num_iterations=3, encoder=None, eps=1e-8, **kwargs): super().__init__(**kwargs) if encoder == None: + def encode_(x): return x + self.encode = encode_ else: self.encode = encoder self.eps = eps self.num_iterations = num_iterations - self.wl = WLSubtreeEmbedding(n,nx,nz,num_iterations) + self.wl = WLSubtreeEmbedding(n, nx, nz, num_iterations) initial_sigma_f = 1 initial_l = 1 self.log_sigma_f = nn.Parameter(torch.log(torch.tensor(initial_sigma_f))) self.log_l = nn.Parameter(torch.log(torch.tensor(initial_l))) + def forward(self, x1, x2, **kwargs): # edge_indexs1 = dense_to_sparse(edge_indexs1)[0] # print(f'in kernel!!!') edge_indices1 = self.encode(x1) edge_indices2 = self.encode(x2) - N1 = x1.shape[0] N2 = x2.shape[0] @@ -197,15 +211,14 @@ def forward(self, x1, x2, **kwargs): v1_norm = v1 / (v1.norm() + self.eps) v2_norm = v2 / (v2.norm() + self.eps) cosine_sim = torch.dot(v1_norm, v2_norm) - K[i, j] = sigma_f**2 * torch.exp(- (1 - cosine_sim) / (l**2)) + K[i, j] = sigma_f**2 * torch.exp(-(1 - cosine_sim) / (l**2)) return K - class TwoDimensionalCCNNEmbedding(nn.Module): - def __init__(self,n,nx,nz,layers=3, output_channel=64,negative_slope=0.2): + def __init__(self, n, nx, nz, layers=3, output_channel=64, negative_slope=0.2): """ - Combinatorial Complex Neural Network based massagepassing layer (returns a cochain) for the CSS kernel + Combinatorial Complex Neural Network based massagepassing layer (returns a cochain) for the CSS kernel """ super().__init__() c_1 = torch.zeros(n, 3) @@ -227,9 +240,7 @@ def __init__(self,n,nx,nz,layers=3, output_channel=64,negative_slope=0.2): negative_slope, ) - - - def forward(self,relations): + def forward(self, relations): x_0, x_1, x_2 = self.ccnn_0( x_0, x_1, @@ -284,20 +295,20 @@ def forward(self,relations): x_0 = torch.nanmean(x_0, dim=0) x_1 = torch.nanmean(x_1, dim=0) x_2 = torch.nanmean(x_2, dim=0) - out = x_0+x_1+x_2 - + out = x_0 + x_1 + x_2 + return out + + class TwoDimensionalCCNNKernel(Kernel): - def __init__(self,n,nx,nz,**kwargs): + def __init__(self, n, nx, nz, **kwargs): super().__init__(**kwargs) - self.ccnn = TwoDimensionalCCNNEmbedding(n,nx,nz) + self.ccnn = TwoDimensionalCCNNEmbedding(n, nx, nz) self.rbf = RBFKernel() self.scale = ScaleKernel(self.rbf) - def forward(self, x1, x2,diag=False,**params): + + def forward(self, x1, x2, diag=False, **params): x = self.ccnn(x1) y = self.ccnn(x2) - return self.scale(x,y,diag=diag,**params) - - - + return self.scale(x, y, diag=diag, **params) diff --git a/bayesian_optimization/objective_function.py b/bayesian_optimization/objective_function.py index 1b19db9..65dd19d 100644 --- a/bayesian_optimization/objective_function.py +++ b/bayesian_optimization/objective_function.py @@ -2,17 +2,21 @@ import numpy as np from evaluation.decoder_based_evaluation import * from code_construction.code_construction import * -from evaluation.circuit_level_noise import MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise +from evaluation.circuit_level_noise import ( + MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise, +) # ========================= # Utilities # ========================= + def _log_binom(n: int, j: int) -> float: """Stable log binomial: log C(n, j) via lgamma.""" return math.lgamma(n + 1) - math.lgamma(j + 1) - math.lgamma(n - j + 1) + def _build_pchip(x, y): """ Try to build a PCHIP interpolator; fall back to linear if SciPy is missing. @@ -20,10 +24,14 @@ def _build_pchip(x, y): """ try: from scipy.interpolate import PchipInterpolator - return PchipInterpolator(np.asarray(x, float), np.asarray(y, float), extrapolate=True), False + + return PchipInterpolator( + np.asarray(x, float), np.asarray(y, float), extrapolate=True + ), False except Exception: return (np.asarray(x, float), np.asarray(y, float)), True + def _eval_interp(interp_obj, xq): """Evaluate either a PCHIP object or a linear interpolator (x, y tuple).""" if isinstance(interp_obj, tuple): @@ -32,6 +40,7 @@ def _eval_interp(interp_obj, xq): else: return interp_obj(np.asarray(xq, float)) + def _eval_piecewise_slope(x, y, tq): """ Compute piecewise-constant slope for linear interpolation fallback. @@ -53,10 +62,12 @@ def _eval_piecewise_slope(x, y, tq): slope = np.where(tq >= x[-1], (y[-1] - y[-2]) / (x[-1] - x[-2]), slope) return slope + # ========================= # f2_converter: t -> f2(t) # ========================= + class f2_converter: r""" Compute and interpolate: @@ -68,7 +79,9 @@ class f2_converter: def __init__(self, n: int, use_pchip: bool = True): self.n = int(n) ln3 = math.log(3.0) - log_terms = np.array([_log_binom(self.n, j) + j * ln3 for j in range(self.n + 1)], dtype=float) + log_terms = np.array( + [_log_binom(self.n, j) + j * ln3 for j in range(self.n + 1)], dtype=float + ) # prefix log-sum-exp for sum_{j=0..t} log_prefix = np.full(self.n + 1, -np.inf, dtype=float) @@ -109,10 +122,12 @@ def df2_dt(self, t): x, y = self._t2f2 return _eval_piecewise_slope(x, y, t) + # ========================================= # pl_t_converter: t <-> pL (with derivative) # ========================================= + class pl_t_converter: r""" Convert between the correctable-weight proxy t and the logical error rate p_L, @@ -129,24 +144,35 @@ def __init__(self, n: int, p_phys: float = 0.01, use_pchip: bool = True): # ----- Precompute log PMF terms ----- log_p = math.log(self.p) log_q = math.log(1.0 - self.p) - log_pmf = np.array([_log_binom(self.n, j) + j * log_p + (self.n - j) * log_q - for j in range(self.n + 1)], dtype=float) + log_pmf = np.array( + [ + _log_binom(self.n, j) + j * log_p + (self.n - j) * log_q + for j in range(self.n + 1) + ], + dtype=float, + ) # Right-tail sums: log_tail[j] = log sum_{i=j..n} pmf(i) log_tail = np.full(self.n + 2, -np.inf, dtype=float) log_tail[self.n] = log_pmf[self.n] for j in range(self.n - 1, -1, -1): m = max(log_pmf[j], log_tail[j + 1]) - log_tail[j] = m + math.log(math.exp(log_pmf[j] - m) + math.exp(log_tail[j + 1] - m)) + log_tail[j] = m + math.log( + math.exp(log_pmf[j] - m) + math.exp(log_tail[j + 1] - m) + ) # p_L(t) ≈ tail at j = floor(t)+1; build table over integer t self.t_grid = np.arange(self.n + 1, dtype=float) - log_pl_table = np.array([log_tail[int(t) + 1] for t in self.t_grid], dtype=float) + log_pl_table = np.array( + [log_tail[int(t) + 1] for t in self.t_grid], dtype=float + ) self.log10_pl_table = log_pl_table / math.log(10.0) # ----- Build interpolators ----- if use_pchip: - self._t2log10pl, self._t_linear = _build_pchip(self.t_grid, self.log10_pl_table) + self._t2log10pl, self._t_linear = _build_pchip( + self.t_grid, self.log10_pl_table + ) if not self._t_linear: self._t2log10pl_deriv = self._t2log10pl.derivative() else: @@ -188,12 +214,14 @@ def pl_to_t(self, k, pl): t_hat = _eval_interp(self._log10pl2t, log10_pl) return t_hat if np.ndim(pl) else float(t_hat) + # ========================================= # ObjectiveFunction (with uncertainty) # ========================================= import torch + class ObjectiveFunction: """ Objective: F(x) = R + f2(t_hat) - 1, where @@ -206,8 +234,15 @@ class ObjectiveFunction: returns (F_mean, F_std), or with aux diagnostics if return_aux=True. """ - def __init__(self, code_constructor, lambda_ = 1,pp=0.01, decoder_param={'trail': 10000,'max_error':100}, - circuit_level_noise=False, circuit_param=None): + def __init__( + self, + code_constructor, + lambda_=1, + pp=0.01, + decoder_param={"trail": 10000, "max_error": 100}, + circuit_level_noise=False, + circuit_param=None, + ): self.code_constructor = code_constructor self.n = int(code_constructor.n) self.pp = float(pp) @@ -223,11 +258,11 @@ def __init__(self, code_constructor, lambda_ = 1,pp=0.01, decoder_param={'trail' if circuit_level_noise: if circuit_param is None: circuit_param = { - 'noise_model':'SD6', - 'num_workers' : 24, - 'rounds':12, - 'custom_error_model':{}, - 'decoder':'bplsd' + "noise_model": "SD6", + "num_workers": 24, + "rounds": 12, + "custom_error_model": {}, + "decoder": "bplsd", } self.circuit_param = circuit_param @@ -252,23 +287,27 @@ def ler(self, css): """Compute the logical error rate, either via circuit-level or decoder-based simulation.""" if self.circuit_level_noise: mc = MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise( - css, noise_model=self.circuit_param['noise_model'], - p=self.pp, rounds=self.circuit_param['rounds'], - custom_error_model=self.circuit_param['custom_error_model'], - decoder=self.circuit_param['decoder'] + css, + noise_model=self.circuit_param["noise_model"], + p=self.pp, + rounds=self.circuit_param["rounds"], + custom_error_model=self.circuit_param["custom_error_model"], + decoder=self.circuit_param["decoder"], + ) + pL = mc.run( + shots=self.decoder_param["trail"], + max_error=self.decoder_param["max_error"], + num_workers=self.circuit_param["num_worker"], ) - pL = mc.run(shots=self.decoder_param['trail'], - max_error=self.decoder_param['max_error'], - num_workers=self.circuit_param['num_worker']) else: evaluator = CSS_Evaluator(css.hx, css.hz) pL = evaluator.Get_logical_error_rate_Monte_Carlo( physical_error_rate=self.pp, xyz_bias=[1, 1, 1], - trail=self.decoder_param.get('trail', 10000) + trail=self.decoder_param.get("trail", 10000), ) - pL = min(pL,1-(1e-8)) - pL = max(pL,1e-20) + pL = min(pL, 1 - (1e-8)) + pL = max(pL, 1e-20) return float(pL) def nller(self, x): @@ -325,16 +364,22 @@ def forward(self, x): # --------------------------- def nlpl_with_std(self, x, pl_total_mean, pl_total_std): """Compute (-log pL, propagated std).""" - return float(-np.log(pl_total_mean)),float( pl_total_std / pl_total_mean) - + return float(-np.log(pl_total_mean)), float(pl_total_std / pl_total_mean) + def nllerpq_with_std(self, x, pl_total_mean, pl_total_std): """Compute mean/std of -log(1 - (1 - pL)^{1/k}).""" css = self.code_constructor.construct(self._to_np_bits(x)) - mean = -np.log(1-(1-pl_total_mean)**(1/css.k)) - std = pl_total_std*(1-pl_total_mean)**(1/css.k-1)/(css.k * (1-(1-pl_total_mean)**(1/css.k))) + mean = -np.log(1 - (1 - pl_total_mean) ** (1 / css.k)) + std = ( + pl_total_std + * (1 - pl_total_mean) ** (1 / css.k - 1) + / (css.k * (1 - (1 - pl_total_mean) ** (1 / css.k))) + ) return float(mean), float(std) - def pl_to_obj_with_std(self, x, pl_total_mean, pl_total_std, return_aux: bool = False): + def pl_to_obj_with_std( + self, x, pl_total_mean, pl_total_std, return_aux: bool = False + ): """ Given the mean/std of the total logical error rate (pL_total), compute the mean/std of the objective F(x). @@ -374,7 +419,6 @@ def pl_to_obj_with_std(self, x, pl_total_mean, pl_total_std, return_aux: bool = # gprime = (1.0 / k) * (1.0 - m_total) ** (1.0 / k - 1.0) # s_per = abs(gprime) * s_total - # # per → t_hat → f2 → F_mean # t_hat = float(self.pl_t_converter.pl_to_t(k=None, pl=m_per)) # f2_val = float(self.f2_converter.t_to_f2(t_hat)) @@ -396,7 +440,7 @@ def pl_to_obj_with_std(self, x, pl_total_mean, pl_total_std, return_aux: bool = F_mean = self.lambda_ * R + f2_val - 1.0 dlog10pl_dt = float(self.pl_t_converter.dlog10pl_dt(t_hat)) - dpl_dt = math.log(10.0) * m * dlog10pl_dt # dp/dt + dpl_dt = math.log(10.0) * m * dlog10pl_dt # dp/dt dt_dpl = 0.0 if abs(dpl_dt) < 1e-300 else 1.0 / dpl_dt df2_dt = float(self.f2_converter.df2_dt(t_hat)) diff --git a/code_construction/code_construction.py b/code_construction/code_construction.py index 97f3560..24d4f6d 100644 --- a/code_construction/code_construction.py +++ b/code_construction/code_construction.py @@ -1,10 +1,10 @@ """ - This file contains the code constructions. +This file contains the code constructions. - The code constructions are the following: - 1.Stabilizer codes constructed by the canonical construction. - 2.CSS code constructed by the QC-LDPC-HGP construction. - 3.CSS code constructed by the Bivariate-bycicle codes. +The code constructions are the following: + 1.Stabilizer codes constructed by the canonical construction. + 2.CSS code constructed by the QC-LDPC-HGP construction. + 3.CSS code constructed by the Bivariate-bycicle codes. """ @@ -12,75 +12,68 @@ from bposd.css import css_code -class StabilizerCode(): - def __init__(self,h) -> None: +class StabilizerCode: + def __init__(self, h) -> None: self.h = h if not self.check_validity(): - raise ValueError('This stabilizer code is not valid.') - self.n = self.h.shape[1]//2 + raise ValueError("This stabilizer code is not valid.") + self.n = self.h.shape[1] // 2 def check_validity(self): if not isinstance(self.h, np.ndarray): return False - if self.h.shape[1]%2 != 0: + if self.h.shape[1] % 2 != 0: return False - n = self.h.shape[1]//2 - omega = np.block([ - [np.zeros((n,n)),np.eye(n)], - [np.eye(n),np.zeros((n,n))] - ]) - if not ((self.h @ omega @ self.h.T) %2 == 0).all(): + n = self.h.shape[1] // 2 + omega = np.block([[np.zeros((n, n)), np.eye(n)], [np.eye(n), np.zeros((n, n))]]) + if not ((self.h @ omega @ self.h.T) % 2 == 0).all(): return False return True - + + class CSSCode(StabilizerCode): - def __init__(self,hx=None,hz=None) -> None: + def __init__(self, hx=None, hz=None) -> None: self.hx = np.array(hx) self.hz = np.array(hz) - self.h = np.block([ - [hz, np.zeros(hz.shape)], - [np.zeros(hx.shape), hx] - ]) + self.h = np.block([[hz, np.zeros(hz.shape)], [np.zeros(hx.shape), hx]]) if not self.check_validity(): - raise ValueError('This CSS code is not valid.') + raise ValueError("This CSS code is not valid.") qcode = css_code(self.hx, self.hz) self.lx = np.array(qcode.lx.toarray()) - self.lz = np.array(qcode.lz.toarray()) # logical operators + self.lz = np.array(qcode.lz.toarray()) # logical operators self.k = qcode.K self.n = qcode.N - - - + def check_validity(self): - if not isinstance(self.hx, np.ndarray)or not isinstance(self.hz, np.ndarray): - print('not np.array') + if not isinstance(self.hx, np.ndarray) or not isinstance(self.hz, np.ndarray): + print("not np.array") return False - if self.hx.shape[1]!=self.hz.shape[1]: - print('n is not equal') + if self.hx.shape[1] != self.hz.shape[1]: + print("n is not equal") return False - if (((self.hz @ self.hx.T)%2) != 0).any(): - print('some x and z stabilizers do not commute') + if (((self.hz @ self.hx.T) % 2) != 0).any(): + print("some x and z stabilizers do not commute") return False return True - -class CodeConstructor(): +class CodeConstructor: """ - This is the class for code construction. - now we support the following methods: - 1. Canonical construction.(for Evolutionary algorithm, general stabilizer codes) - 2. QC-LDPC-HGP construction. - 3. Bivariate-bycicle construction. - 4. Rotated Surface code construction - 5. PG-LDPC-HGP construction + This is the class for code construction. + now we support the following methods: + 1. Canonical construction.(for Evolutionary algorithm, general stabilizer codes) + 2. QC-LDPC-HGP construction. + 3. Bivariate-bycicle construction. + 4. Rotated Surface code construction + 5. PG-LDPC-HGP construction """ - def __init__(self,method='qc-ldpc-hgp',para_dict=None) -> None: + + def __init__(self, method="qc-ldpc-hgp", para_dict=None) -> None: """ - method(str): The method of the code construction, 'canonical', 'qc-ldpc-hgp', 'bivariate-bycicle','rotated-surface','qc-gb'. + method(str): The method of the code construction, 'canonical', 'qc-ldpc-hgp', 'bivariate-bycicle','rotated-surface','qc-gb'. + - """ self.method = method self.para_dict = para_dict @@ -88,271 +81,314 @@ def __init__(self,method='qc-ldpc-hgp',para_dict=None) -> None: self.nx = 0 self.nz = 0 self.k = None - + if not self.check_parameters_validity(): - raise ValueError('The parameters are not valid.') - + raise ValueError("The parameters are not valid.") + def check_parameters_validity(self): - if self.method == 'canonical' or self.method == 'canonical_css': + if self.method == "canonical" or self.method == "canonical_css": return self.check_canonical_parameters_validity() - elif self.method == 'qc-ldpc-hgp' or self.method == 'pg-ldpc-hgp': + elif self.method == "qc-ldpc-hgp" or self.method == "pg-ldpc-hgp": return self.check_qc_ldpc_hgp_parameters_validity() # elif self.method == 'bivariate-bycicle': - # return self.check_bivariate_bycicle_parameters_validity() - elif self.method == 'rotated-surface': + # return self.check_bivariate_bycicle_parameters_validity() + elif self.method == "rotated-surface": return True - elif self.method in ['qc-gb','bivariate-bycicle','bb','symmetric-qc-gb']: - if self.method in ['bb','bivariate-bycicle']: + elif self.method in ["qc-gb", "bivariate-bycicle", "bb", "symmetric-qc-gb"]: + if self.method in ["bb", "bivariate-bycicle"]: try: - self.n = self.para_dict['l'] * self.para_dict['g'] + self.n = self.para_dict["l"] * self.para_dict["g"] self.nx = self.n self.nz = self.n except: - raise ValueError('failing setting n,nx,nz') - if 'g' in self.para_dict.keys(): - if type(self.para_dict['g']) == type(int(1)): + raise ValueError("failing setting n,nx,nz") + if "g" in self.para_dict.keys(): + if type(self.para_dict["g"]) == type(int(1)): return True - elif 'ga' in self.para_dict.keys() and 'gb' in self.para_dict.keys(): - if type(self.para_dict['ga']) == type(int(1)) and type(self.para_dict['gb']) == type(int(1)): + elif "ga" in self.para_dict.keys() and "gb" in self.para_dict.keys(): + if type(self.para_dict["ga"]) == type(int(1)) and type( + self.para_dict["gb"] + ) == type(int(1)): return True return False else: - raise ValueError('The method is not supported.') - def construct(self,parameters): - if self.method == 'canonical': + raise ValueError("The method is not supported.") + + def construct(self, parameters): + if self.method == "canonical": return self.canonical_construction(parameters) - elif self.method == 'canonical_css': - return self.canonical_construction(parameters,CSS=True) - elif self.method == 'qc-ldpc-hgp': + elif self.method == "canonical_css": + return self.canonical_construction(parameters, CSS=True) + elif self.method == "qc-ldpc-hgp": return self.qc_ldpc_hgp_construction(parameters) - elif self.method == 'pg-ldpc-hgp': + elif self.method == "pg-ldpc-hgp": return self.pg_ldpc_hgp_construction(parameters) # elif self.method == 'bivariate-bycicle': # return self.bivariate_bycicle_construction(parameters) - elif self.method == 'rotated-surface': + elif self.method == "rotated-surface": return self.rotated_surface_construction(parameters) - elif self.method == 'qc-gb': + elif self.method == "qc-gb": return self.quasi_cyclic_generalized_bicycle_code(parameters) - elif self.method == 'bivariate-bycicle' or self.method == 'bb': + elif self.method == "bivariate-bycicle" or self.method == "bb": return self.arbitrary_bivariate_bicycle_code(parameters) - elif self.method == 'symmetric-qc-gb': + elif self.method == "symmetric-qc-gb": return self.symmetric_quasi_cyclic_generalized_bicycle_code(parameters) else: - raise ValueError('The method is not supported.') + raise ValueError("The method is not supported.") def check_canonical_parameters_validity(self): """ - For each element in canonical construction requires the following parameters: - 1. n(int): The number of qubits. - 2. k(int): The number of logical qubits. - 3. r(int): The number of stabilizer generators at least have one x-operator. - and s+r = n-k + For each element in canonical construction requires the following parameters: + 1. n(int): The number of qubits. + 2. k(int): The number of logical qubits. + 3. r(int): The number of stabilizer generators at least have one x-operator. + and s+r = n-k """ for index in self.para_dict.keys(): - if type(self.para_dict[index])!=int or self.para_dict[index]<=0: + if type(self.para_dict[index]) != int or self.para_dict[index] <= 0: return False - if not index in {'n','k','r'}: + if not index in {"n", "k", "r"}: return False - if self.para_dict['n']-self.para_dict['k'] < self.para_dict['r']: + if self.para_dict["n"] - self.para_dict["k"] < self.para_dict["r"]: return False - self.n = self.para_dict['n'] - self.k = self.para_dict['k'] - self.nx = self.para_dict['r'] - self.nz = self.n-self.k-self.nx + self.n = self.para_dict["n"] + self.k = self.para_dict["k"] + self.nx = self.para_dict["r"] + self.nz = self.n - self.k - self.nx return True - + def check_qc_ldpc_hgp_parameters_validity(self): """ - For each element in QC-LDPC-HGP construction requires the following parameters: - 1. p(int): The number of rows of the matrix M. - 2. q(int): The number of columns of the matrix M. - 3. m(int): The size of the quasi-cyclic matrix. - 4*. p_2(int): The number of rows of the matrix M_2. (* means optional) - 5*. q_2(int): The number of columns of the matrix M_2. - 6*. m_2(int): The size of the quasi-cyclic matrix M_2. + For each element in QC-LDPC-HGP construction requires the following parameters: + 1. p(int): The number of rows of the matrix M. + 2. q(int): The number of columns of the matrix M. + 3. m(int): The size of the quasi-cyclic matrix. + 4*. p_2(int): The number of rows of the matrix M_2. (* means optional) + 5*. q_2(int): The number of columns of the matrix M_2. + 6*. m_2(int): The size of the quasi-cyclic matrix M_2. """ for index in self.para_dict.keys(): - if type(self.para_dict[index])!=int or self.para_dict[index]<=0: + if type(self.para_dict[index]) != int or self.para_dict[index] <= 0: return False - for i in {'m','p','q'}: + for i in {"m", "p", "q"}: if not i in self.para_dict.keys(): return False - if 'p_2' in self.para_dict.keys(): - if not 'q_2' in self.para_dict.keys(): + if "p_2" in self.para_dict.keys(): + if not "q_2" in self.para_dict.keys(): return False - if not 'm_2' in self.para_dict.keys(): + if not "m_2" in self.para_dict.keys(): return False - self.n = self.para_dict['m']*self.para_dict['m_2']*(self.para_dict['p']*self.para_dict['p_2']+self.para_dict['q']*self.para_dict['q_2']) - self.nx = self.para_dict['m']*self.para_dict['m_2']*self.para_dict['p']*self.para_dict['q_2'] - self.nz = self.para_dict['m']*self.para_dict['m_2']*self.para_dict['p_2']*self.para_dict['q'] + self.n = ( + self.para_dict["m"] + * self.para_dict["m_2"] + * ( + self.para_dict["p"] * self.para_dict["p_2"] + + self.para_dict["q"] * self.para_dict["q_2"] + ) + ) + self.nx = ( + self.para_dict["m"] + * self.para_dict["m_2"] + * self.para_dict["p"] + * self.para_dict["q_2"] + ) + self.nz = ( + self.para_dict["m"] + * self.para_dict["m_2"] + * self.para_dict["p_2"] + * self.para_dict["q"] + ) else: - self.n = self.para_dict['m']**2*(self.para_dict['p']**2 + self.para_dict['q']**2) - self.nx = self.para_dict['m']**2*self.para_dict['p']*self.para_dict['q'] + self.n = self.para_dict["m"] ** 2 * ( + self.para_dict["p"] ** 2 + self.para_dict["q"] ** 2 + ) + self.nx = ( + self.para_dict["m"] ** 2 * self.para_dict["p"] * self.para_dict["q"] + ) self.nz = self.nx - - - + return True - + def check_bivariate_bycicle_parameters_validity(self): # print('check bb') for index in self.para_dict.keys(): - if type(self.para_dict[index])!=int or self.para_dict[index]<0: + if type(self.para_dict[index]) != int or self.para_dict[index] < 0: return False - for i in {'l','m'}: + for i in {"l", "m"}: if not i in self.para_dict.keys(): return False - self.n = 2*self.para_dict['l'] * self.para_dict['m'] - self.nx = self.n//2 + self.n = 2 * self.para_dict["l"] * self.para_dict["m"] + self.nx = self.n // 2 self.nx = self.nx return True - - - def rotated_surface_construction(self,p): + def rotated_surface_construction(self, p): """ p*p rotated surface code """ - def index(row, col,reversed=False): - if row<0 or row>=p or col<0 or col>=p: + + def index(row, col, reversed=False): + if row < 0 or row >= p or col < 0 or col >= p: return -1 if reversed: return row + col * p return row * p + col + if p % 2 == 0: raise ValueError("p must be an odd number.") N = int(p * p) - stabilizers_num = int((p-1)*(p-1)/2+p-1) + stabilizers_num = int((p - 1) * (p - 1) / 2 + p - 1) Hx = np.zeros((stabilizers_num, N), dtype=int) Hz = np.zeros((stabilizers_num, N), dtype=int) - x_stabilizers = [[] for i in range(stabilizers_num)] z_stabilizers = [[] for i in range(stabilizers_num)] - for i in range(p+1): - for j in range((p-1)//2): - if index(i-1,j+(i+1)%2)!= -1: - x_stabilizers[i*(p-1)//2+j].append(index(i-1,2*j+(i+1)%2)) - if index(i-1,j+(i+1)%2+1)!= -1: - x_stabilizers[i*(p-1)//2+j].append(index(i-1,2*j+(i+1)%2+1)) - if index(i,j+(i+1)%2)!= -1: - x_stabilizers[i*(p-1)//2+j].append(index(i,2*j+(i+1)%2)) - if index(i,j+(i+1)%2+1)!= -1: - x_stabilizers[i*(p-1)//2+j].append(index(i,2*j+(i+1)%2+1)) - for i in range(p+1): - for j in range((p-1)//2): - if index(i-1,j+(i)%2,reversed=True)!= -1: - z_stabilizers[i*(p-1)//2+j].append(index(i-1,2*j+(i)%2,reversed=True)) - if index(i-1,j+(i)%2+1,reversed=True)!= -1: - z_stabilizers[i*(p-1)//2+j].append(index(i-1,2*j+(i)%2+1,reversed=True)) - if index(i,j+(i)%2,reversed=True)!= -1: - z_stabilizers[i*(p-1)//2+j].append(index(i,2*j+(i)%2,reversed=True)) - if index(i,j+(i)%2+1,reversed=True)!= -1: - z_stabilizers[i*(p-1)//2+j].append(index(i,2*j+(i)%2+1,reversed=True)) + for i in range(p + 1): + for j in range((p - 1) // 2): + if index(i - 1, j + (i + 1) % 2) != -1: + x_stabilizers[i * (p - 1) // 2 + j].append( + index(i - 1, 2 * j + (i + 1) % 2) + ) + if index(i - 1, j + (i + 1) % 2 + 1) != -1: + x_stabilizers[i * (p - 1) // 2 + j].append( + index(i - 1, 2 * j + (i + 1) % 2 + 1) + ) + if index(i, j + (i + 1) % 2) != -1: + x_stabilizers[i * (p - 1) // 2 + j].append( + index(i, 2 * j + (i + 1) % 2) + ) + if index(i, j + (i + 1) % 2 + 1) != -1: + x_stabilizers[i * (p - 1) // 2 + j].append( + index(i, 2 * j + (i + 1) % 2 + 1) + ) + for i in range(p + 1): + for j in range((p - 1) // 2): + if index(i - 1, j + (i) % 2, reversed=True) != -1: + z_stabilizers[i * (p - 1) // 2 + j].append( + index(i - 1, 2 * j + (i) % 2, reversed=True) + ) + if index(i - 1, j + (i) % 2 + 1, reversed=True) != -1: + z_stabilizers[i * (p - 1) // 2 + j].append( + index(i - 1, 2 * j + (i) % 2 + 1, reversed=True) + ) + if index(i, j + (i) % 2, reversed=True) != -1: + z_stabilizers[i * (p - 1) // 2 + j].append( + index(i, 2 * j + (i) % 2, reversed=True) + ) + if index(i, j + (i) % 2 + 1, reversed=True) != -1: + z_stabilizers[i * (p - 1) // 2 + j].append( + index(i, 2 * j + (i) % 2 + 1, reversed=True) + ) for i in range(stabilizers_num): for j in x_stabilizers[i]: - Hx[i,j] = 1 + Hx[i, j] = 1 for j in z_stabilizers[i]: - Hz[i,j] = 1 - + Hz[i, j] = 1 + return CSSCode(Hx, Hz) - - def canonical_construction(self,bitstring,CSS=False) -> StabilizerCode: + + def canonical_construction(self, bitstring, CSS=False) -> StabilizerCode: """ - Construct the stabilizer code by the canonical construction. + Construct the stabilizer code by the canonical construction. - Args: - parameters(np.array): The parameters of the code. + Args: + parameters(np.array): The parameters of the code. - Returns: - stabilizer_code(StabilizerCode): The stabilizer code. + Returns: + stabilizer_code(StabilizerCode): The stabilizer code. """ - + n = self.n k = self.k - r = self.para_dict['r'] - s = n-k-r - + r = self.para_dict["r"] + s = n - k - r + if CSS == False: - if len(bitstring)!=((n-r)*r+k*(n-k)+(1+r)*r//2): - raise ValueError('The length of the bitstring is invalid.') + if len(bitstring) != ((n - r) * r + k * (n - k) + (1 + r) * r // 2): + raise ValueError("The length of the bitstring is invalid.") for i in bitstring: - if i!=0 and i!=1: + if i != 0 and i != 1: raise ValueError("The bitstring's value is invalid.") - A = bitstring[:r*(n-r)].reshape((r,n-r)) - A_1 = A[:,:s] - A_2 = A[:,s:] - C = bitstring[r*(n-r):r*(n-r)+k*(n-k)].reshape((n-k,k)) + A = bitstring[: r * (n - r)].reshape((r, n - r)) + A_1 = A[:, :s] + A_2 = A[:, s:] + C = bitstring[r * (n - r) : r * (n - r) + k * (n - k)].reshape((n - k, k)) C_1 = C[:r] C_2 = C[r:] - M = np.zeros((r,r)) + M = np.zeros((r, r)) # Reconstruct the symmetric matrix M - p=0 + p = 0 for i in range(r): - for j in range(i,r): - - M[i,j] = bitstring[r*(n-r)+k*(n-k)+p] - M[j,i] = M[i,j] + for j in range(i, r): + M[i, j] = bitstring[r * (n - r) + k * (n - k) + p] + M[j, i] = M[i, j] p += 1 - if s!= 0: - D = (A_1.T + C_2@A_2.T)%2 - B = (C_1@A_2.T + M)%2 - - H = np.block([ - [np.eye(r), A_1, A_2, B, np.zeros((r,s)),C_1], - [np.zeros((s,r)), np.zeros((s,s)),np.zeros((s,k)),D,np.eye(s),C_2] - ]) + if s != 0: + D = (A_1.T + C_2 @ A_2.T) % 2 + B = (C_1 @ A_2.T + M) % 2 + + H = np.block( + [ + [np.eye(r), A_1, A_2, B, np.zeros((r, s)), C_1], + [ + np.zeros((s, r)), + np.zeros((s, s)), + np.zeros((s, k)), + D, + np.eye(s), + C_2, + ], + ] + ) else: - - - B = (C_1@A_2.T + M)%2 + B = (C_1 @ A_2.T + M) % 2 - H = np.block([ - np.eye(r), A_2, B,C_1 - ]) + H = np.block([np.eye(r), A_2, B, C_1]) return StabilizerCode(H) else: - if len(bitstring)!=((n-r)*r+s*k): - raise ValueError('The length of the bitstring is invalid.') + if len(bitstring) != ((n - r) * r + s * k): + raise ValueError("The length of the bitstring is invalid.") for i in bitstring: - if i!=0 and i!=1: + if i != 0 and i != 1: raise ValueError("The bitstring's value is invalid.") - A = bitstring[:r*(n-r)].reshape((r,n-r)) - A_1 = A[:,:s] - A_2 = A[:,s:] - C = bitstring[r*(n-r):r*(n-r)+k*(n-k)].reshape((s,k)) - D = (A_1.T + C@A_2.T)%2 - hx = np.block([ - D, np.eye(s), C - ]) - hz = np.block([ - np.eye(r), A_1, A_2 - ]) - return CSSCode(hx,hz) - - - - def qc_ldpc_hgp_construction(self,M,M_2 = None ,form = None) -> CSSCode: + A = bitstring[: r * (n - r)].reshape((r, n - r)) + A_1 = A[:, :s] + A_2 = A[:, s:] + C = bitstring[r * (n - r) : r * (n - r) + k * (n - k)].reshape((s, k)) + D = (A_1.T + C @ A_2.T) % 2 + hx = np.block([D, np.eye(s), C]) + hz = np.block([np.eye(r), A_1, A_2]) + return CSSCode(hx, hz) + + def qc_ldpc_hgp_construction(self, M, M_2=None, form=None) -> CSSCode: """ - Construct the CSS code by the QC-LDPC-HGP construction. + Construct the CSS code by the QC-LDPC-HGP construction. - Args: - M(np.ndarray): The parameters of the code.M is a np.ndarray - M_2*(np.ndarray): The parameters of the code.M_2 is a np.ndarray + Args: + M(np.ndarray): The parameters of the code.M is a np.ndarray + M_2*(np.ndarray): The parameters of the code.M_2 is a np.ndarray - Returns: - CSS_code(CSSCode): The CSS code. + Returns: + CSS_code(CSSCode): The CSS code. """ if M_2 is None: - H1 = self.ldpc_construction(self.para_dict['p'],self.para_dict['q'],self.para_dict['m'],M) - H2 = self.ldpc_construction(self.para_dict['p'],self.para_dict['q'],self.para_dict['m'],M) + H1 = self.ldpc_construction( + self.para_dict["p"], self.para_dict["q"], self.para_dict["m"], M + ) + H2 = self.ldpc_construction( + self.para_dict["p"], self.para_dict["q"], self.para_dict["m"], M + ) else: - H1 = self.ldpc_construction(self.para_dict['p'],self.para_dict['q'],self.para_dict['m'],M) - H2 = self.ldpc_construction(self.para_dict['p_2'],self.para_dict['q_2'],self.para_dict['m_2'],M_2) - r1,n1 = H1.shape - r2,n2 = H2.shape + H1 = self.ldpc_construction( + self.para_dict["p"], self.para_dict["q"], self.para_dict["m"], M + ) + H2 = self.ldpc_construction( + self.para_dict["p_2"], self.para_dict["q_2"], self.para_dict["m_2"], M_2 + ) + r1, n1 = H1.shape + r2, n2 = H2.shape # HX = [H1 ⊗ In2 | Ir1 ⊗ H2.T] # HZ = [In1 ⊗ H2 | H1.T ⊗ Ir2] HX_left = np.kron(H1, np.eye(n2)) @@ -363,15 +399,14 @@ def qc_ldpc_hgp_construction(self,M,M_2 = None ,form = None) -> CSSCode: HZ_right = np.kron(H1.T, np.eye(r2)) HZ = np.hstack((HZ_left, HZ_right)) - - return CSSCode(HX,HZ) - - def ldpc_construction(self,p,q,m,M): + return CSSCode(HX, HZ) + + def ldpc_construction(self, p, q, m, M): # print(M) - if len(M)!=p*q: - raise ValueError('The shape of M is invalid!') - M = M.reshape(p,q) - H = np.zeros((p*m,q*m)) + if len(M) != p * q: + raise ValueError("The shape of M is invalid!") + M = M.reshape(p, q) + H = np.zeros((p * m, q * m)) # Define the base cyclic shift matrix S (m x m) S = np.zeros((m, m)) @@ -382,55 +417,55 @@ def ldpc_construction(self,p,q,m,M): for i in range(p): for j in range(q): # Get the value from matrix M - shift = M[i, j] % (m+1) - + shift = M[i, j] % (m + 1) + # Create the shifted version of S based on the value in M if shift == 0: - H_ij = np.zeros((m,m)) # Zero matrix for shift 0 + H_ij = np.zeros((m, m)) # Zero matrix for shift 0 else: H_ij = np.linalg.matrix_power(S, shift) - + # Place H_ij in the corresponding block of H - H[i * m: (i + 1) * m, j * m: (j + 1) * m] = H_ij + H[i * m : (i + 1) * m, j * m : (j + 1) * m] = H_ij return H - def bivariate_bycicle_construction(self,parameters) -> CSSCode: - ''' + def bivariate_bycicle_construction(self, parameters) -> CSSCode: + """ para_dict={ 'm'=int, # n = 2lm - 'l'=int, + 'l'=int, parameters = [boolean,boolean,boolean,boolean,boolean,boolean,int,int,int,int,int,int] } - ''' - m = self.para_dict['m'] - l = self.para_dict['l'] - Sm = np.zeros((m,m)) - Sl = np.zeros((l,l)) + """ + m = self.para_dict["m"] + l = self.para_dict["l"] + Sm = np.zeros((m, m)) + Sl = np.zeros((l, l)) # A_p = para_dict['A'] # B_p = para_dict['B'] - A_p = np.array([(parameters[i]%2,parameters[i+6]) for i in range(3)]) - B_p = np.array([(parameters[i]%2,parameters[i+6]) for i in range(3,6)]) + A_p = np.array([(parameters[i] % 2, parameters[i + 6]) for i in range(3)]) + B_p = np.array([(parameters[i] % 2, parameters[i + 6]) for i in range(3, 6)]) for i in range(m): - Sm[i][(i+1) % m]=1 + Sm[i][(i + 1) % m] = 1 for j in range(l): - Sl[j][(j+1) % l]=1 + Sl[j][(j + 1) % l] = 1 # print(f'SL:\n{Sl}\n,Sm:\n{Sm}') - x = np.kron(Sl,np.eye(m)) - y = np.kron(np.eye(l),Sm) + x = np.kron(Sl, np.eye(m)) + y = np.kron(np.eye(l), Sm) # print(f'x:\n{x}\n,y:\n{y}') - A = np.zeros((l*m,l*m)) - B= np.zeros((l*m,l*m)) + A = np.zeros((l * m, l * m)) + B = np.zeros((l * m, l * m)) for i in range(len(A_p)): - if A_p[i][0]==0: - A += np.linalg.matrix_power(x , (A_p[i][1] % l)) + if A_p[i][0] == 0: + A += np.linalg.matrix_power(x, (A_p[i][1] % l)) else: - A += np.linalg.matrix_power(y , (A_p[i][1] % l)) - if B_p[i][0]==0: - B += np.linalg.matrix_power(x , (B_p[i][1] % m)) + A += np.linalg.matrix_power(y, (A_p[i][1] % l)) + if B_p[i][0] == 0: + B += np.linalg.matrix_power(x, (B_p[i][1] % m)) else: - B += np.linalg.matrix_power(y , (B_p[i][1] % m)) + B += np.linalg.matrix_power(y, (B_p[i][1] % m)) A = A % 2 B = B % 2 # for i in A: @@ -439,15 +474,20 @@ def bivariate_bycicle_construction(self,parameters) -> CSSCode: # print() # print(f'A:\n{A}\n,B:\n{B}') HX = np.hstack((A, B)) - HZ = np.hstack((B.T,A.T)) + HZ = np.hstack((B.T, A.T)) # print(f'n:{2*l*m}\n,rank(A):\n{np.linalg.matrix_rank(A)},\n\nrank(B):\n{np.linalg.matrix_rank(B)}') - css = CSSCode(HX,HZ) + css = CSSCode(HX, HZ) return css - def pg_ldpc_hgp_construction(self,M): - H1 = self.pg_ldpc_construction(self.para_dict['p'],self.para_dict['q'],self.para_dict['m'],M) - H2 = self.pg_ldpc_construction(self.para_dict['p'],self.para_dict['q'],self.para_dict['m'],M) - r1,n1 = H1.shape - r2,n2 = H2.shape + + def pg_ldpc_hgp_construction(self, M): + H1 = self.pg_ldpc_construction( + self.para_dict["p"], self.para_dict["q"], self.para_dict["m"], M + ) + H2 = self.pg_ldpc_construction( + self.para_dict["p"], self.para_dict["q"], self.para_dict["m"], M + ) + r1, n1 = H1.shape + r2, n2 = H2.shape # HX = [H1 ⊗ In2 | Ir1 ⊗ H2.T] # HZ = [In1 ⊗ H2 | H1.T ⊗ Ir2] HX_left = np.kron(H1, np.eye(n2)) @@ -458,104 +498,108 @@ def pg_ldpc_hgp_construction(self,M): HZ_right = np.kron(H1.T, np.eye(r2)) HZ = np.hstack((HZ_left, HZ_right)) - - return CSSCode(HX,HZ) + return CSSCode(HX, HZ) - def pg_ldpc_construction(self,p,q,m,M): - ''' + def pg_ldpc_construction(self, p, q, m, M): + """ M has p*q elements in the permutation group; each element is represented by a permutation from 0 to m-1 M is a p*q * m long np.array - ''' - H = np.zeros((m*p,m*q)) - for i in range(p*q): + """ + H = np.zeros((m * p, m * q)) + for i in range(p * q): for j in range(m): - x = int(i//q + j) - y = int(i%q + M[m*i + j]) - H[x,y] = 1 + x = int(i // q + j) + y = int(i % q + M[m * i + j]) + H[x, y] = 1 return H - def quasi_cyclic_generalized_bicycle_code(self,parameters): - if 'g' in self.para_dict.keys(): - g_size_a = self.para_dict['g'] - g_size_b = self.para_dict['g'] + + def quasi_cyclic_generalized_bicycle_code(self, parameters): + if "g" in self.para_dict.keys(): + g_size_a = self.para_dict["g"] + g_size_b = self.para_dict["g"] else: - g_size_a = int(self.para_dict['ga']) - g_size_b = int(self.para_dict['gb']) - A = parameters['A'] - B = parameters['B'] + g_size_a = int(self.para_dict["ga"]) + g_size_b = int(self.para_dict["gb"]) + A = parameters["A"] + B = parameters["B"] # print(g) if A.shape[0] != A.shape[1]: - raise ValueError('A should have a square shape') + raise ValueError("A should have a square shape") if B.shape[0] != B.shape[1]: - raise ValueError('B should have a square shape') + raise ValueError("B should have a square shape") if g_size_a * A.shape[0] != g_size_b * B.shape[0]: - raise ValueError('The shape of B(A) and B(B) is not equal.') - A = parameters['A'] - B = parameters['B'] - B_A = self.corresponding_matrix_BA(g_size_a,A) - B_B = self.corresponding_matrix_BA(g_size_b,B) + raise ValueError("The shape of B(A) and B(B) is not equal.") + A = parameters["A"] + B = parameters["B"] + B_A = self.corresponding_matrix_BA(g_size_a, A) + B_B = self.corresponding_matrix_BA(g_size_b, B) # check = (B_B.T@B_A.T+B_A.T@B_B.T)%2 # print(check.any()) - H_x = np.hstack((B_A,B_B)) - H_z = np.hstack((B_B.T,B_A.T)) + H_x = np.hstack((B_A, B_B)) + H_z = np.hstack((B_B.T, B_A.T)) # check = (H_z @ H_x.T + B_B.T@B_A.T+B_A.T@B_B.T)%2 # print(check.any()) # return H_x,H_z - return CSSCode(H_x,H_z) - + return CSSCode(H_x, H_z) - def corresponding_matrix_BA(self,g_size,A): - r,c = A.shape[0],A.shape[1] + def corresponding_matrix_BA(self, g_size, A): + r, c = A.shape[0], A.shape[1] for i in range(r): for j in range(c): - if j==0: - r_matrix = self.corresponding_matrix_Ba(g_size,A[i][j]) + if j == 0: + r_matrix = self.corresponding_matrix_Ba(g_size, A[i][j]) else: - r_matrix = np.hstack((r_matrix,self.corresponding_matrix_Ba(g_size,A[i][j]))) - if i==0: + r_matrix = np.hstack( + (r_matrix, self.corresponding_matrix_Ba(g_size, A[i][j])) + ) + if i == 0: B_A = r_matrix else: - B_A = np.vstack((B_A,r_matrix)) + B_A = np.vstack((B_A, r_matrix)) return B_A - def corresponding_matrix_Ba(self,g_size,a): - B_a = np.zeros((g_size,g_size),dtype=int) + def corresponding_matrix_Ba(self, g_size, a): + B_a = np.zeros((g_size, g_size), dtype=int) for i in range(g_size): - B_a = (B_a + a[i] * self.g_power(g_size,i))%2 + B_a = (B_a + a[i] * self.g_power(g_size, i)) % 2 return B_a - def g_power(self,g_size,p): - g_p = np.zeros((g_size,g_size),dtype=int) + def g_power(self, g_size, p): + + g_p = np.zeros((g_size, g_size), dtype=int) for i in range(g_size): - g_p[i][int((i+p)%g_size)]=1 + g_p[i][int((i + p) % g_size)] = 1 return g_p - def arbitrary_bivariate_bicycle_code(self,parameters): + + def arbitrary_bivariate_bicycle_code(self, parameters): # a=[a_0,a_1,...,a_{l+g-2}] is a rep of coefficient of A=a_0*I + a_1*x + a_2*x^2 +...+ a_{l-1}*x^{l-1} + a_l*y + a_{l+1}*y^2 +...+ a_{l+g-2}*y^{g-1} - l = self.para_dict['l'] - g = self.para_dict['g'] - a = parameters[:(l+g-1)] - b = parameters[(l+g-1):] - A = np.zeros((l,l,g),dtype=int) - B = np.zeros((l,l,g),dtype=int) + l = self.para_dict["l"] + g = self.para_dict["g"] + a = parameters[: (l + g - 1)] + b = parameters[(l + g - 1) :] + A = np.zeros((l, l, g), dtype=int) + B = np.zeros((l, l, g), dtype=int) # x: g_l \otimes I_g for i in range(l): for j in range(l): # a_j = 1 if a[j] == 1: - A[i][(i+j)%l][0]=(A[i][(i+j)%l][0]+1)%2 + A[i][(i + j) % l][0] = (A[i][(i + j) % l][0] + 1) % 2 if b[j] == 1: - B[i][(i+j)%l][0]=(B[i][(i+j)%l][0]+1)%2 + B[i][(i + j) % l][0] = (B[i][(i + j) % l][0] + 1) % 2 # y: I_l \otimes g_g - for i in range(l): - for j in range(g-1): - if a[j+l] == 1: - A[i][i][j+1]=(A[i][i][j+1]+1)%2 - if b[j+l] == 1: - B[i][i][j+1]=(B[i][i][j+1]+1)%2 - return self.quasi_cyclic_generalized_bicycle_code({'A':A,'B':B}) - def symmetric_quasi_cyclic_generalized_bicycle_code(self,A): + for i in range(l): + for j in range(g - 1): + if a[j + l] == 1: + A[i][i][j + 1] = (A[i][i][j + 1] + 1) % 2 + if b[j + l] == 1: + B[i][i][j + 1] = (B[i][i][j + 1] + 1) % 2 + return self.quasi_cyclic_generalized_bicycle_code({"A": A, "B": B}) + + def symmetric_quasi_cyclic_generalized_bicycle_code(self, A): B = A r = A.shape[0] c = A.shape[1] @@ -563,14 +607,5 @@ def symmetric_quasi_cyclic_generalized_bicycle_code(self,A): for i in range(r): for j in range(c): for k in range(g): - B[i][j][k] = A[j][i][(g-k)%g] - return self.quasi_cyclic_generalized_bicycle_code({'A':A,'B':B}) - - - - - - - - - + B[i][j][k] = A[j][i][(g - k) % g] + return self.quasi_cyclic_generalized_bicycle_code({"A": A, "B": B}) diff --git a/dataset.py b/dataset.py index 755117f..7cce574 100644 --- a/dataset.py +++ b/dataset.py @@ -3,142 +3,179 @@ from evaluation.decoder_based_evaluation import CSS_Evaluator from code_construction.code_construction import CodeConstructor from torch.utils.data import Dataset -from evaluation.circuit_level_noise import MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise - - -class Get_new_points_function(): - def __init__(self,method='qc-ldpc-hgp',hyperparameters = {'p': 2, 'q': 6, 'm': 2},encode='None',param={'p':0.5}): +from evaluation.circuit_level_noise import ( + MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise, +) + + +class Get_new_points_function: + def __init__( + self, + method="qc-ldpc-hgp", + hyperparameters={"p": 2, "q": 6, "m": 2}, + encode="None", + param={"p": 0.5}, + ): self.method = method self.hyperparameters = hyperparameters - self.code_constructor = CodeConstructor(method,hyperparameters) + self.code_constructor = CodeConstructor(method, hyperparameters) self.encode = encode self.init = False self.param = param - def get_new_points_function(self,number): - if self.method == 'qc-ldpc-hgp': + def get_new_points_function(self, number): + if self.method == "qc-ldpc-hgp": new_points = self.get_new_points_HGP(number) - elif self.method == 'bb': - new_points = self.get_new_bb_vector(number,self.param) + elif self.method == "bb": + new_points = self.get_new_bb_vector(number, self.param) return new_points - - def get_new_points_HGP(self,number): - return np.random.randint(0, self.hyperparameters['m'] + 1, (number, self.hyperparameters['p'] * self.hyperparameters['q'])) - def get_new_bb_vector(self,number,param={'p':0.5}): - density_expectation = param['p'] - results = [] - l = self.hyperparameters['l'] - g = self.hyperparameters['g'] + def get_new_points_HGP(self, number): + return np.random.randint( + 0, + self.hyperparameters["m"] + 1, + (number, self.hyperparameters["p"] * self.hyperparameters["q"]), + ) - while number>0: - new_point = np.random.choice([0,1], size=(l+g-1)*2,p=[1-density_expectation,density_expectation]) + def get_new_bb_vector(self, number, param={"p": 0.5}): + density_expectation = param["p"] + results = [] + l = self.hyperparameters["l"] + g = self.hyperparameters["g"] + + while number > 0: + new_point = np.random.choice( + [0, 1], + size=(l + g - 1) * 2, + p=[1 - density_expectation, density_expectation], + ) c = self.code_constructor.construct(new_point) - if c.k==0: + if c.k == 0: continue else: results.append(new_point) number -= 1 return np.array(results) - - class QEC_Dataset(Dataset): - def __init__(self,l,g,load = False,number = 100,save = True,p = 0.05,gnp_param = {'p':0.5},path = './data/codes/',noise_model='depolarizing',noise_and_decoder_param={}): + def __init__( + self, + l, + g, + load=False, + number=100, + save=True, + p=0.05, + gnp_param={"p": 0.5}, + path="./data/codes/", + noise_model="depolarizing", + noise_and_decoder_param={}, + ): # l = 6 # g = 3 - para_dict = {'l':l,'g':g} + para_dict = {"l": l, "g": g} self.noise_model = noise_model self.p = p - - self.codeconstructor = CodeConstructor(method='bb',para_dict = para_dict) - self.gnp = Get_new_points_function(method='bb',hyperparameters = para_dict,param=gnp_param).get_new_points_function + + self.codeconstructor = CodeConstructor(method="bb", para_dict=para_dict) + self.gnp = Get_new_points_function( + method="bb", hyperparameters=para_dict, param=gnp_param + ).get_new_points_function + def objectivefunction(code): - if noise_model == 'depolarizing': + if noise_model == "depolarizing": evaluator = CSS_Evaluator(code.hx, code.hz) pL = evaluator.Get_logical_error_rate_Monte_Carlo( physical_error_rate=p, xyz_bias=[1, 1, 1], - trail=noise_and_decoder_param.get('trail',10_000) + trail=noise_and_decoder_param.get("trail", 10_000), ) else: - rounds = noise_and_decoder_param.get('rounds',12) - decoder = noise_and_decoder_param.get('decoder','bplsd') - custom_error_model = noise_and_decoder_param.get('custom_error_model',{}) + rounds = noise_and_decoder_param.get("rounds", 12) + decoder = noise_and_decoder_param.get("decoder", "bplsd") + custom_error_model = noise_and_decoder_param.get( + "custom_error_model", {} + ) css = self.codeconstructor.construct(code) mc = MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise( - css, - noise_model=noise_model, - p=p, - rounds=rounds, - custom_error_model=custom_error_model, - decoder=decoder - ) - pL = mc.run(shots= noise_and_decoder_param.get('trail',100_000), max_error = noise_and_decoder_param.get('max_error',100), num_workers = noise_and_decoder_param.get('num_worker',24)) + css, + noise_model=noise_model, + p=p, + rounds=rounds, + custom_error_model=custom_error_model, + decoder=decoder, + ) + pL = mc.run( + shots=noise_and_decoder_param.get("trail", 100_000), + max_error=noise_and_decoder_param.get("max_error", 100), + num_workers=noise_and_decoder_param.get("num_worker", 24), + ) return pL + self.objectfunction = objectivefunction # self.normalizer = Normalizer(mode='log_pos_trans',possitive=True) self.number = number self.load = load if load == False: - - self.get_a_dataset(l,g,number,save,path=path) + self.get_a_dataset(l, g, number, save, path=path) else: - self.load_dataset(l,g,number,path=path) + self.load_dataset(l, g, number, path=path) + def __len__(self): return self.number - def get_a_dataset(self,l,g,number,save,path = './data/codes/'): + def get_a_dataset(self, l, g, number, save, path="./data/codes/"): def get_k(x): x = self.codeconstructor.construct(x) return x.k + X = [] y = [] - print('Generating dataset...') - - + print("Generating dataset...") + # target = 200 # sample = 0 number1 = number - while number1>0: + while number1 > 0: c = self.gnp(1) - if get_k(c[0])!=0: + if get_k(c[0]) != 0: # target-=1 - number1-=1 + number1 -= 1 X.append(c[0]) - # sample+=1 + # sample+=1 pl = self.objectfunction(c[0]) y.append(pl) - print(f'code[{number-number1}],logical error rate:{pl}') + print(f"code[{number - number1}],logical error rate:{pl}") self.X = np.array(X) self.y = np.array(y) if save: - with open(path+f'{l}_{g}_{number}_{self.noise_model}.pkl','wb') as f: - pickle.dump((X,y),f) - return X,y - - def load_dataset(self,l,g,number,path = './data/codes/'): - print('loading') - file_name = path+f'{l}_{g}_{number}_{self.noise_model}.pkl' - with open(file_name, 'rb') as f: - X,y = pickle.load(f) + with open(path + f"{l}_{g}_{number}_{self.noise_model}.pkl", "wb") as f: + pickle.dump((X, y), f) + return X, y + + def load_dataset(self, l, g, number, path="./data/codes/"): + print("loading") + file_name = path + f"{l}_{g}_{number}_{self.noise_model}.pkl" + with open(file_name, "rb") as f: + X, y = pickle.load(f) self.X = X self.y = y - print('successfully loaded') - - def __getitem__(self,idx): + print("successfully loaded") + + def __getitem__(self, idx): return self.X[idx], self.y[idx] - + from torch.utils.data import Subset + class QECSubset(Subset): def __init__(self, base_dataset, indices): - super().__init__(base_dataset,indices) + super().__init__(base_dataset, indices) self.X = base_dataset.X[indices] self.y = base_dataset.y[indices] self.indices = indices @@ -147,4 +184,4 @@ def __len__(self): return len(self.X) def __getitem__(self, idx): - return self.X[idx], self.y[idx] \ No newline at end of file + return self.X[idx], self.y[idx] diff --git a/evaluation/circuit_level_noise.py b/evaluation/circuit_level_noise.py index 6e4ce24..65601d2 100644 --- a/evaluation/circuit_level_noise.py +++ b/evaluation/circuit_level_noise.py @@ -1,4 +1,4 @@ -import stim,sinter +import stim, sinter import matplotlib.pyplot as plt from code_construction.code_construction import CodeConstructor import numpy as np @@ -25,80 +25,106 @@ class CircuitGenerator: def __init__(self, css): self.hx = np.array(css.hx, dtype=np.uint8) % 2 self.hz = np.array(css.hz, dtype=np.uint8) % 2 - self.n = int(self.hx.shape[1]) + self.n = int(self.hx.shape[1]) assert self.hz.shape[1] == self.n self.mX = int(self.hx.shape[0]) self.mZ = int(self.hz.shape[0]) - self.Lx = None if getattr(css, 'lx', None) is None else (np.array(css.lx, dtype=np.uint8) % 2) - self.Lz = None if getattr(css, 'lz', None) is None else (np.array(css.lz, dtype=np.uint8) % 2) - if self.Lx is not None: assert self.Lx.shape[1] == self.n - if self.Lz is not None: assert self.Lz.shape[1] == self.n + self.Lx = ( + None + if getattr(css, "lx", None) is None + else (np.array(css.lx, dtype=np.uint8) % 2) + ) + self.Lz = ( + None + if getattr(css, "lz", None) is None + else (np.array(css.lz, dtype=np.uint8) % 2) + ) + if self.Lx is not None: + assert self.Lx.shape[1] == self.n + if self.Lz is not None: + assert self.Lz.shape[1] == self.n self.data_start = 0 - self.data_end = self.n + self.data_end = self.n self.ancZ_start = self.data_end - self.ancZ_end = self.ancZ_start + self.mZ + self.ancZ_end = self.ancZ_start + self.mZ self.ancX_start = self.ancZ_end - self.ancX_end = self.ancX_start + self.mX + self.ancX_end = self.ancX_start + self.mX + + def _supp_hz(self, j): + return np.flatnonzero(self.hz[j]).tolist() + + def _supp_hx(self, j): + return np.flatnonzero(self.hx[j]).tolist() + def _supp_Lz(self, j): + return np.flatnonzero(self.Lz[j]).tolist() - def _supp_hz(self, j): return np.flatnonzero(self.hz[j]).tolist() - def _supp_hx(self, j): return np.flatnonzero(self.hx[j]).tolist() - def _supp_Lz(self, j): return np.flatnonzero(self.Lz[j]).tolist() - def _supp_Lx(self, j): return np.flatnonzero(self.Lx[j]).tolist() + def _supp_Lx(self, j): + return np.flatnonzero(self.Lx[j]).tolist() - def dataqubit_preparation(self,pp,pd,data_prep,measure_data): + def dataqubit_preparation(self, pp, pd, data_prep, measure_data): c = stim.Circuit() data_idxs = list(range(self.data_start, self.data_end)) if self.n > 0: - prep_mode = data_prep if data_prep != 'auto' else ('X' if measure_data == 'X' else 'Z') - if prep_mode == 'Z': - c.append_operation('R', data_idxs) - c.append_operation('X_ERROR', data_idxs, pp) - elif prep_mode == 'X': - c.append_operation('RX', data_idxs) - c.append_operation('Z_ERROR', data_idxs, pp) - elif prep_mode != 'none': + prep_mode = ( + data_prep + if data_prep != "auto" + else ("X" if measure_data == "X" else "Z") + ) + if prep_mode == "Z": + c.append_operation("R", data_idxs) + c.append_operation("X_ERROR", data_idxs, pp) + elif prep_mode == "X": + c.append_operation("RX", data_idxs) + c.append_operation("Z_ERROR", data_idxs, pp) + elif prep_mode != "none": raise ValueError(f"invalid data_prep={data_prep!r}") if pd > 0: - c.append_operation('DEPOLARIZE1', data_idxs, pd) + c.append_operation("DEPOLARIZE1", data_idxs, pd) return c - def measureancilla_preparation(self,pp): + + def measureancilla_preparation(self, pp): c = stim.Circuit() - ancX = list(range(self.ancX_start,self.ancX_end)) - ancZ = list(range(self.ancZ_start,self.ancZ_end)) - c.append_operation('R',ancX) - c.append_operation('R',ancZ) - c.append_operation('X_ERROR',ancX,pp) - c.append_operation('X_ERROR',ancZ,pp) + ancX = list(range(self.ancX_start, self.ancX_end)) + ancZ = list(range(self.ancZ_start, self.ancZ_end)) + c.append_operation("R", ancX) + c.append_operation("R", ancZ) + c.append_operation("X_ERROR", ancX, pp) + c.append_operation("X_ERROR", ancZ, pp) return c - def cnotcircuit(self,pd,pg): - ancX = list(range(self.ancX_start,self.ancX_end)) - ancZ = list(range(self.ancZ_start,self.ancZ_end)) + + def cnotcircuit(self, pd, pg): + ancX = list(range(self.ancX_start, self.ancX_end)) + ancZ = list(range(self.ancZ_start, self.ancZ_end)) data_idxs = list(range(self.data_start, self.data_end)) c = stim.Circuit() - c.append_operation('H',ancX) - c.append_operation('DEPOLARIZE1',ancX,pd) - c.append_operation('TICK') + c.append_operation("H", ancX) + c.append_operation("DEPOLARIZE1", ancX, pd) + c.append_operation("TICK") cnot_gate_set = [] ordered_cnot_gate_set = [] # get all the cnot gates for tar in range(self.mZ): ctrls = self._supp_hz(tar) for ctrl in ctrls: - cnot_gate_set.append((data_idxs[ctrl],ancZ[tar])) - + cnot_gate_set.append((data_idxs[ctrl], ancZ[tar])) # arranging cnot gates for cnot in cnot_gate_set: placed_flag = False - + for order in range(len(ordered_cnot_gate_set)): placable_flag = True for arranged_cnot in ordered_cnot_gate_set[order]: - if arranged_cnot[0] == cnot[0] or arranged_cnot[0] == cnot[1] or arranged_cnot[1] == cnot[0] or arranged_cnot[1] == cnot[1]: + if ( + arranged_cnot[0] == cnot[0] + or arranged_cnot[0] == cnot[1] + or arranged_cnot[1] == cnot[0] + or arranged_cnot[1] == cnot[1] + ): placable_flag = False if placable_flag: ordered_cnot_gate_set[order].append(cnot) @@ -110,23 +136,28 @@ def cnotcircuit(self,pd,pg): # adding cnots to the circuit for cnots in ordered_cnot_gate_set: for cnot in cnots: - c.append_operation('CX',cnot) - c.append_operation('DEPOLARIZE2',cnot) - c.append_operation('TICK') - - cnot_gate_set= [] + c.append_operation("CX", cnot) + c.append_operation("DEPOLARIZE2", cnot) + c.append_operation("TICK") + + cnot_gate_set = [] ordered_cnot_gate_set = [] for ctrl in range(self.mX): tars = self._supp_hx(ctrl) for tar in tars: - cnot_gate_set.append((ancX[ctrl],data_idxs[tar])) + cnot_gate_set.append((ancX[ctrl], data_idxs[tar])) for cnot in cnot_gate_set: placed_flag = False - + for order in range(len(ordered_cnot_gate_set)): placable_flag = True for arranged_cnot in ordered_cnot_gate_set[order]: - if arranged_cnot[0] == cnot[0] or arranged_cnot[0] == cnot[1] or arranged_cnot[1] == cnot[0] or arranged_cnot[1] == cnot[1]: + if ( + arranged_cnot[0] == cnot[0] + or arranged_cnot[0] == cnot[1] + or arranged_cnot[1] == cnot[0] + or arranged_cnot[1] == cnot[1] + ): placable_flag = False if placable_flag: ordered_cnot_gate_set[order].append(cnot) @@ -138,118 +169,134 @@ def cnotcircuit(self,pd,pg): # adding cnots to the circuit for cnots in ordered_cnot_gate_set: for cnot in cnots: - c.append_operation('CX',cnot) - c.append_operation('DEPOLARIZE2',cnot) - c.append_operation('TICK') + c.append_operation("CX", cnot) + c.append_operation("DEPOLARIZE2", cnot) + c.append_operation("TICK") - c.append_operation('H',ancX) - c.append_operation('DEPOLARIZE1',ancX,pd) - c.append_operation('TICK') + c.append_operation("H", ancX) + c.append_operation("DEPOLARIZE1", ancX, pd) + c.append_operation("TICK") return c - def measurements(self,ps): - ancX = list(range(self.ancX_start,self.ancX_end)) - ancZ = list(range(self.ancZ_start,self.ancZ_end)) + + def measurements(self, ps): + ancX = list(range(self.ancX_start, self.ancX_end)) + ancZ = list(range(self.ancZ_start, self.ancZ_end)) c = stim.Circuit() - c.append_operation('X_ERROR',ancZ,ps) - c.append_operation('X_ERROR',ancX,ps) - c.append_operation('MR',ancZ) - c.append_operation('MR',ancX) - c.append_operation('X_ERROR',ancZ,ps) - c.append_operation('X_ERROR',ancX,ps) + c.append_operation("X_ERROR", ancZ, ps) + c.append_operation("X_ERROR", ancX, ps) + c.append_operation("MR", ancZ) + c.append_operation("MR", ancX) + c.append_operation("X_ERROR", ancZ, ps) + c.append_operation("X_ERROR", ancX, ps) return c - def generate( self, - rounds=1, + rounds=1, noise_model=None, burn_in_rounds=1, - measure_data='Z', # 'Z' or 'X' + measure_data="Z", # 'Z' or 'X' add_observable=True, - data_prep='auto', # 'auto'|'Z'|'X'|'none' - add_endcaps=False # start False; turn True + data_prep="auto", # 'auto'|'Z'|'X'|'none' + add_endcaps=False, # start False; turn True ) -> stim.Circuit: if noise_model is None: - noise_model = {'pd':0.0, 'pg':0.0, 'ps':0.0, 'pp':0.0} - pd = float(noise_model.get('pd', 0.0)) - pg = float(noise_model.get('pg', 0.0)) - ps = float(noise_model.get('ps', 0.0)) - pp = float(noise_model.get('pp', 0.0)) + noise_model = {"pd": 0.0, "pg": 0.0, "ps": 0.0, "pp": 0.0} + pd = float(noise_model.get("pd", 0.0)) + pg = float(noise_model.get("pg", 0.0)) + ps = float(noise_model.get("ps", 0.0)) + pp = float(noise_model.get("pp", 0.0)) - assert measure_data in ('Z', 'X') + assert measure_data in ("Z", "X") assert rounds >= 1 and burn_in_rounds >= 0 c = stim.Circuit() data_idxs = list(range(self.data_start, self.data_end)) # ---- data preparation (once) ---- - c += self.dataqubit_preparation(pp,pd,data_prep,measure_data) + c += self.dataqubit_preparation(pp, pd, data_prep, measure_data) c += self.measureancilla_preparation(pp) - c.append_operation('DEPOLARIZE1',data_idxs,pd) - c += self.cnotcircuit(pd,pg) + c.append_operation("DEPOLARIZE1", data_idxs, pd) + c += self.cnotcircuit(pd, pg) c += self.measurements(ps) - if measure_data == 'Z': + if measure_data == "Z": for i in range(self.mZ): - c.append_operation('DETECTOR',stim.target_rec(i-self.mX-self.mZ)) + c.append_operation("DETECTOR", stim.target_rec(i - self.mX - self.mZ)) else: for i in range(self.mX): - c.append_operation('DETECTOR',stim.target_rec(i-self.mX)) - + c.append_operation("DETECTOR", stim.target_rec(i - self.mX)) # repeat: rep = stim.Circuit() - rep.append_operation('TICK') - rep.append_operation('DEPOLARIZE1',data_idxs) - rep += self.cnotcircuit(pd,pg) + rep.append_operation("TICK") + rep.append_operation("DEPOLARIZE1", data_idxs) + rep += self.cnotcircuit(pd, pg) rep += self.measurements(ps) - for i in range(self.mZ+self.mX): - rep.append_operation('DETECTOR',[stim.target_rec(i-self.mX-self.mZ),stim.target_rec(i-2*(self.mX+self.mZ))]) + for i in range(self.mZ + self.mX): + rep.append_operation( + "DETECTOR", + [ + stim.target_rec(i - self.mX - self.mZ), + stim.target_rec(i - 2 * (self.mX + self.mZ)), + ], + ) if rounds > 1: - c += (rounds-1) * rep - + c += (rounds - 1) * rep - # final detectors # final observables - if measure_data == 'Z': - c.append('X_ERROR',data_idxs,ps) - c.append('MZ',data_idxs) - for det in range(self.mZ): - tars = self._supp_hz(det) - rec = [] - for i in tars: - rec.append(i-self.n) - rec.append(det-self.mZ-self.mX-self.n) - c.append_operation('DETECTOR',[stim.target_rec(i) for i in rec]) - for logical in range(len(self.Lz)): - tars = self._supp_Lz(logical) - c.append_operation( "OBSERVABLE_INCLUDE", [stim.target_rec(k-self.n) for k in tars], [logical] ) - else: - c.append('Z_ERROR',data_idxs,ps) - c.append('MX',data_idxs) - for det in range(self.mX): - tars = self._supp_hx(det) - rec = [] - for i in tars: - rec.append(i-self.n) - rec.append(det-self.mX-self.n) - c.append_operation('DETECTOR',[stim.target_rec(i) for i in rec]) - for logical in range(len(self.Lx)): - tars = self._supp_Lx(logical) - c.append_operation( "OBSERVABLE_INCLUDE", [stim.target_rec(k-self.n) for k in tars], [logical] ) - + if measure_data == "Z": + c.append("X_ERROR", data_idxs, ps) + c.append("MZ", data_idxs) + for det in range(self.mZ): + tars = self._supp_hz(det) + rec = [] + for i in tars: + rec.append(i - self.n) + rec.append(det - self.mZ - self.mX - self.n) + c.append_operation("DETECTOR", [stim.target_rec(i) for i in rec]) + for logical in range(len(self.Lz)): + tars = self._supp_Lz(logical) + c.append_operation( + "OBSERVABLE_INCLUDE", + [stim.target_rec(k - self.n) for k in tars], + [logical], + ) + else: + c.append("Z_ERROR", data_idxs, ps) + c.append("MX", data_idxs) + for det in range(self.mX): + tars = self._supp_hx(det) + rec = [] + for i in tars: + rec.append(i - self.n) + rec.append(det - self.mX - self.n) + c.append_operation("DETECTOR", [stim.target_rec(i) for i in rec]) + for logical in range(len(self.Lx)): + tars = self._supp_Lx(logical) + c.append_operation( + "OBSERVABLE_INCLUDE", + [stim.target_rec(k - self.n) for k in tars], + [logical], + ) return c - - class MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise: - def __init__(self, css, noise_model='SD6', p=0.01, rounds=1, - custom_error_model={}, decoder='bplsd', decoder_kwargs=None): + def __init__( + self, + css, + noise_model="SD6", + p=0.01, + rounds=1, + custom_error_model={}, + decoder="bplsd", + decoder_kwargs=None, + ): """ decoder: 'pymatching' | 'bplsd' decoder_kwargs: only used when decoder='bplsd', passed into SinterLsdDecoder(...) @@ -259,16 +306,20 @@ def __init__(self, css, noise_model='SD6', p=0.01, rounds=1, self.p = p self.rounds = rounds self.custom_error_model = custom_error_model - self.decoder = (decoder or 'pymatching').lower() + self.decoder = (decoder or "pymatching").lower() default_bplsd = dict( - max_iter=5, - bp_method='ms', - ms_scaling_factor=0.8, - schedule='parallel', - lsd_order=7, + max_iter=5, + bp_method="ms", + ms_scaling_factor=0.8, + schedule="parallel", + lsd_order=7, + ) + self.decoder_kwargs = ( + default_bplsd + if (decoder_kwargs is None) + else {**default_bplsd, **decoder_kwargs} ) - self.decoder_kwargs = default_bplsd if (decoder_kwargs is None) else {**default_bplsd, **decoder_kwargs} return @staticmethod @@ -278,69 +329,87 @@ def _json_safe(x): if isinstance(x, (np.generic,)): return x.item() if isinstance(x, dict): - return {str(k): MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise._json_safe(v) - for k, v in x.items()} + return { + str( + k + ): MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise._json_safe( + v + ) + for k, v in x.items() + } if isinstance(x, (list, tuple, set)): - return [MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise._json_safe(v) for v in x] + return [ + MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise._json_safe( + v + ) + for v in x + ] if isinstance(x, (str, int, float, bool)) or x is None: return x return str(x) - def get_error_param(self, noise_model: str, p: float, custom_error_model={}) -> dict: + def get_error_param( + self, noise_model: str, p: float, custom_error_model={} + ) -> dict: noise_model = noise_model.upper() - if noise_model not in ['SD6', 'SI1000', 'EM3', 'CUSTOM']: + if noise_model not in ["SD6", "SI1000", "EM3", "CUSTOM"]: raise ValueError(f"Unknown noise model: {noise_model}") - if noise_model == 'SD6': - err = {'pd': p, 'pg': p, 'ps': p, 'pp': p} - elif noise_model == 'SI1000': - err = {'pd': 0.1*p, 'pg': 1.0*p, 'ps': 0.5*p, 'pp': 0.5*p} - elif noise_model == 'EM3': - err = {'pd': 0.25*p, 'pg': 1.0*p, 'ps': 0.5*p, 'pp': 0.5*p} - elif noise_model == 'CUSTOM': + if noise_model == "SD6": + err = {"pd": p, "pg": p, "ps": p, "pp": p} + elif noise_model == "SI1000": + err = {"pd": 0.1 * p, "pg": 1.0 * p, "ps": 0.5 * p, "pp": 0.5 * p} + elif noise_model == "EM3": + err = {"pd": 0.25 * p, "pg": 1.0 * p, "ps": 0.5 * p, "pp": 0.5 * p} + elif noise_model == "CUSTOM": err = custom_error_model return err - def get_sinter_task(self, - css, - mode='both', # 'X'|'Z' - order='ZX', - noise_model='SD6', - p=0.01, - rounds=1, - burn_in_rounds=1, - custom_error_model={}): - + def get_sinter_task( + self, + css, + mode="both", # 'X'|'Z' + order="ZX", + noise_model="SD6", + p=0.01, + rounds=1, + burn_in_rounds=1, + custom_error_model={}, + ): css_code_circuit_generator = CircuitGenerator(css) - error_param = self.get_error_param(noise_model=noise_model, p=p, custom_error_model=custom_error_model) + error_param = self.get_error_param( + noise_model=noise_model, p=p, custom_error_model=custom_error_model + ) mode_up = mode.upper() - if mode_up not in ('X', 'Z'): + if mode_up not in ("X", "Z"): raise ValueError(f"mode must be 'X' or 'Z', got {mode!r}") - measure_data = 'X' if mode_up == 'X' else 'Z' + measure_data = "X" if mode_up == "X" else "Z" circuit = css_code_circuit_generator.generate( rounds=rounds, noise_model=error_param, burn_in_rounds=burn_in_rounds, - measure_data=measure_data, - add_observable=True + measure_data=measure_data, + add_observable=True, ) meta = { - 'p': self.p, - 'n': getattr(css, 'n', None), - 'k': getattr(css, 'k', None), - 'hx': getattr(css, 'hx', None), - 'hz': getattr(css, 'hz', None), - 'lx': getattr(css, 'lx', None), - 'lz': getattr(css, 'lz', None), - 'rounds': rounds, - 'burn_in_rounds': burn_in_rounds, - 'mode': mode, - 'error_model': noise_model, - 'custom_error_model': custom_error_model if noise_model.lower() == 'custom' else None, + "p": self.p, + "n": getattr(css, "n", None), + "k": getattr(css, "k", None), + "hx": getattr(css, "hx", None), + "hz": getattr(css, "hz", None), + "lx": getattr(css, "lx", None), + "lz": getattr(css, "lz", None), + "rounds": rounds, + "burn_in_rounds": burn_in_rounds, + "mode": mode, + "error_model": noise_model, + "custom_error_model": custom_error_model + if noise_model.lower() == "custom" + else None, } meta = self._json_safe(meta) @@ -348,54 +417,62 @@ def get_sinter_task(self, def _choose_decoder(self): - if self.decoder == 'bplsd': + if self.decoder == "bplsd": try: from ldpc.sinter_decoders.sinter_lsd_decoder import SinterLsdDecoder except Exception as ex: - raise RuntimeError( - "decoder='bplsd' need ldpc package" - ) from ex + raise RuntimeError("decoder='bplsd' need ldpc package") from ex - decoders = ['bplsd'] - custom_decoders = { - 'bplsd': SinterLsdDecoder(**self.decoder_kwargs) - } + decoders = ["bplsd"] + custom_decoders = {"bplsd": SinterLsdDecoder(**self.decoder_kwargs)} else: - - decoders = ['pymatching'] + decoders = ["pymatching"] custom_decoders = None return decoders, custom_decoders def run(self, shots: int = 1_000, max_error: int = 100, num_workers: int = 4): decoders_ZX, custom_ZX = self._choose_decoder() - samples_Z = sinter.collect( - tasks=self.get_sinter_task(self.css, mode='Z', - noise_model=self.noise_model, p=self.p, - rounds=self.rounds, burn_in_rounds=1, - custom_error_model=self.custom_error_model), + tasks=self.get_sinter_task( + self.css, + mode="Z", + noise_model=self.noise_model, + p=self.p, + rounds=self.rounds, + burn_in_rounds=1, + custom_error_model=self.custom_error_model, + ), decoders=decoders_ZX, custom_decoders=custom_ZX, - max_shots=shots//2, max_errors=max_error//2, num_workers=num_workers, + max_shots=shots // 2, + max_errors=max_error // 2, + num_workers=num_workers, ) - sZ = sum(getattr(r, 'shots', getattr(r, 'shot_count', 0)) for r in samples_Z) - eZ = sum(getattr(r, 'errors', getattr(r, 'error_count', 0)) for r in samples_Z) + sZ = sum(getattr(r, "shots", getattr(r, "shot_count", 0)) for r in samples_Z) + eZ = sum(getattr(r, "errors", getattr(r, "error_count", 0)) for r in samples_Z) if sZ == 0: raise RuntimeError("No shots collected for Z task.") pZ = eZ / sZ samples_X = sinter.collect( - tasks=self.get_sinter_task(self.css, mode='X', - noise_model=self.noise_model, p=self.p, - rounds=self.rounds, burn_in_rounds=1, - custom_error_model=self.custom_error_model), + tasks=self.get_sinter_task( + self.css, + mode="X", + noise_model=self.noise_model, + p=self.p, + rounds=self.rounds, + burn_in_rounds=1, + custom_error_model=self.custom_error_model, + ), decoders=decoders_ZX, custom_decoders=custom_ZX, - max_shots=shots//2, max_errors=max_error//2, num_workers=num_workers, + max_shots=shots // 2, + max_errors=max_error // 2, + num_workers=num_workers, ) - sX = sum(getattr(r, 'shots', getattr(r, 'shot_count', 0)) for r in samples_X) - eX = sum(getattr(r, 'errors', getattr(r, 'error_count', 0)) for r in samples_X) + sX = sum(getattr(r, "shots", getattr(r, "shot_count", 0)) for r in samples_X) + eX = sum(getattr(r, "errors", getattr(r, "error_count", 0)) for r in samples_X) if sX == 0: raise RuntimeError("No shots collected for X task.") pX = eX / sX @@ -403,37 +480,145 @@ def run(self, shots: int = 1_000, max_error: int = 100, num_workers: int = 4): pL = 1 - (1 - pX) * (1 - pZ) return pL -if __name__ == '__main__': + +if __name__ == "__main__": l = 12 g = 6 - code_constructor = CodeConstructor(method='bb', para_dict={'l': l, 'g': g}) + code_constructor = CodeConstructor(method="bb", para_dict={"l": l, "g": g}) gross_code = [ - 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., - 0., 0., 0., 0., 0., 0., 0., 1., 0., 0. + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + ] + code_144_8 = [ + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + ] + code_144_26 = [ + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, ] - code_144_8 = [0., 1., 1., 0., 1., 1., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1., 1., 1., - 1., 1., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 1., 0., 0., 1.] - code_144_26 = [0., 1., 1., 0., 1., 1., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1., 1., 1., - 1., 1., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 1., 0., 0., 1.] css = code_constructor.construct(gross_code) # css = CodeConstructor('rotated-surface').construct(3) - code_name = ['gross_code','bb code [144,26]','bb code [144,8]'] - i=0 - for code_ in [gross_code,code_144_26,code_144_8]: + code_name = ["gross_code", "bb code [144,26]", "bb code [144,8]"] + i = 0 + for code_ in [gross_code, code_144_26, code_144_8]: p_list = [] pl_list = [] - for ppp in np.linspace(4.8,6.2,4): + for ppp in np.linspace(4.8, 6.2, 4): p = np.exp(-ppp) p_list.append(p) css = code_constructor.construct(code_) - mc = MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise(css,noise_model='SD6',p=p,rounds=12,custom_error_model={},decoder='bplsd') - pl = mc.run(shots= 100_000, max_error = 1000, num_workers = 24) + mc = MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise( + css, + noise_model="SD6", + p=p, + rounds=12, + custom_error_model={}, + decoder="bplsd", + ) + pl = mc.run(shots=100_000, max_error=1000, num_workers=24) pl_list.append(pl) - print(f'physical error rate:{p},logical error rate:{pl}') - plt.plot(p_list,pl_list,label=f'{code_name[i]}') - i+=1 - plt.yscale('log') - plt.xscale('log') + print(f"physical error rate:{p},logical error rate:{pl}") + plt.plot(p_list, pl_list, label=f"{code_name[i]}") + i += 1 + plt.yscale("log") + plt.xscale("log") plt.legend() plt.show() diff --git a/evaluation/css_decode_sim.py b/evaluation/css_decode_sim.py index 32248d7..45c9754 100644 --- a/evaluation/css_decode_sim.py +++ b/evaluation/css_decode_sim.py @@ -167,7 +167,6 @@ def _single_run(self): # randomly generate the error self.error_x, self.error_z = self._generate_error() - if self.channel_update is None: # decode z @@ -257,7 +256,7 @@ def _encoded_error_rates(self): # calculate the residual error residual_x = (self.error_x + self.bpd_x.osdw_decoding) % 2 residual_z = (self.error_z + self.bpd_z.osdw_decoding) % 2 - print(f'new syndrome:{self.hz@residual_x %2}') + print(f"new syndrome:{self.hz @ residual_x % 2}") # check for logical X-error if (self.lz @ residual_x % 2).any(): @@ -524,7 +523,7 @@ def run_decode_sim(self): self._single_run() pbar.set_description( - f"d_max: {self.min_logical_weight}; OSDW_WER: {self.osdw_word_error_rate*100:.3g}±{self.osdw_word_error_rate_eb*100:.2g}%; OSDW: {self.osdw_logical_error_rate*100:.3g}±{self.osdw_logical_error_rate_eb*100:.2g}%; OSD0: {self.osd0_logical_error_rate*100:.3g}±{self.osd0_logical_error_rate_eb*100:.2g}%;" + f"d_max: {self.min_logical_weight}; OSDW_WER: {self.osdw_word_error_rate * 100:.3g}±{self.osdw_word_error_rate_eb * 100:.2g}%; OSDW: {self.osdw_logical_error_rate * 100:.3g}±{self.osdw_logical_error_rate_eb * 100:.2g}%; OSD0: {self.osd0_logical_error_rate * 100:.3g}±{self.osd0_logical_error_rate_eb * 100:.2g}%;" ) current_time = time.time() @@ -568,4 +567,4 @@ def output_dict(self): if key in self.output_keys: output_dict[key] = value # return output_dict - return json.dumps(output_dict, sort_keys=True, indent=4) \ No newline at end of file + return json.dumps(output_dict, sort_keys=True, indent=4) diff --git a/evaluation/decoder_based_evaluation.py b/evaluation/decoder_based_evaluation.py index 6a86f52..4290ca0 100644 --- a/evaluation/decoder_based_evaluation.py +++ b/evaluation/decoder_based_evaluation.py @@ -13,10 +13,12 @@ from bposd.css import css_code -def binomial_probability(n, d_e,p_p): - return comb(n, d_e) * (p_p ** d_e) * ((1 - p_p) ** (n - d_e)) +def binomial_probability(n, d_e, p_p): + return comb(n, d_e) * (p_p**d_e) * ((1 - p_p) ** (n - d_e)) + + def gf2_rref(H): - """ Transform the matrix H to row echelon form """ + """Transform the matrix H to row echelon form""" H = H.copy() rows, cols = H.shape r = 0 # The row we're working on @@ -27,29 +29,32 @@ def gf2_rref(H): continue # No pivot in this column, skip it pivot_row = pivot[0] + r H[[r, pivot_row]] = H[[pivot_row, r]] - + # Zero out all other entries in this column for ri in range(rows): if ri != r and H[ri, c] == 1: H[ri] ^= H[r] - + r += 1 if r == rows: break - + return H + def find_null_space(H): - """ calculate the null space of the matrix H """ + """calculate the null space of the matrix H""" # Transform H to row echelon form rref = gf2_rref(H) rows, cols = rref.shape - pivot_cols = np.array([np.where(rref[r])[0][0] for r in range(rows) if np.any(rref[r])]) + pivot_cols = np.array( + [np.where(rref[r])[0][0] for r in range(rows) if np.any(rref[r])] + ) free_cols = np.array([c for c in range(cols) if c not in pivot_cols]) - + # Generate the matrix G G = np.zeros((len(free_cols), cols), dtype=int) - + # Set the identity matrix in the free columns for i, f_col in enumerate(free_cols): G[i, f_col] = 1 # set the free variable @@ -58,23 +63,24 @@ def find_null_space(H): break if rref[r, f_col] == 1: G[i, p_col] = 1 # set the pivot variable - + return G + def Get_G_of_check_matrix(H): G = find_null_space(H) return G.T -def Get_distribution_dij(G,trails=1000): - d_ij={} + + +def Get_distribution_dij(G, trails=1000): + d_ij = {} for i in range(trails): - x_i = np.random.randint(0,2,(G.shape[1],1)) - x_j = np.random.randint(0,2,(G.shape[1],1)) + x_i = np.random.randint(0, 2, (G.shape[1], 1)) + x_j = np.random.randint(0, 2, (G.shape[1], 1)) if (x_i == x_j).all(): continue else: - - - d = (G@x_i%2 + G@x_j%2)%2 + d = (G @ x_i % 2 + G @ x_j % 2) % 2 d = np.sum(d) if d in d_ij: d_ij[d] += 1 @@ -83,91 +89,100 @@ def Get_distribution_dij(G,trails=1000): return d_ij -class CSS_Evaluator(): - def __init__(self,hx,hz): +class CSS_Evaluator: + def __init__(self, hx, hz): # print(f'initializing') self.hx = hx self.hz = hz qcode = css_code(self.hx, self.hz) self.lx = qcode.lx - self.lz = qcode.lz # logical operators + self.lz = qcode.lz # logical operators self.k = qcode.K self.n = qcode.N # print(f'initialized,n:{self.n},k:{self.k}') - - def Get_error_rate(self,physical_error_rate=0.01,trail=2000): + def Get_error_rate(self, physical_error_rate=0.01, trail=2000): if self.k == 0: return 1 decoder_sim = css_decode_sim( hx=self.hx, hz=self.hz, - error_rate= physical_error_rate, - xyz_error_bias= [1,1, 1], - target_runs= trail, - seed= 0, - bp_method= "minimum_sum", - ms_scaling_factor= 0, - max_iter= self.n*2, - osd_method= "osd_cs", - osd_order= 2, - save_interval= 2, - output_file= None, - check_code= 0, - tqdm_disable= 0, - run_sim= 0, - channel_update= None, - hadamard_rotate= 0, - hadamard_rotate_sector1_length= 0, - error_bar_precision_cutoff= 1e-4 + error_rate=physical_error_rate, + xyz_error_bias=[1, 1, 1], + target_runs=trail, + seed=0, + bp_method="minimum_sum", + ms_scaling_factor=0, + max_iter=self.n * 2, + osd_method="osd_cs", + osd_order=2, + save_interval=2, + output_file=None, + check_code=0, + tqdm_disable=0, + run_sim=0, + channel_update=None, + hadamard_rotate=0, + hadamard_rotate_sector1_length=0, + error_bar_precision_cutoff=1e-4, ) logical_error_rate = decoder_sim.run_decode_sim() return logical_error_rate - def Get_logical_error_rate_Monte_Carlo(self,physical_error_rate= 0.01,xyz_bias = [1,1,1],trail=10000): + + def Get_logical_error_rate_Monte_Carlo( + self, physical_error_rate=0.01, xyz_bias=[1, 1, 1], trail=10000 + ): if self.k == 0: return 1 # print('start to get errors') - errors = self.Get_errors(trail,physical_error_rate=physical_error_rate,xyz_bias=xyz_bias) + errors = self.Get_errors( + trail, physical_error_rate=physical_error_rate, xyz_bias=xyz_bias + ) fail = 0 self.init_decoder(physical_error_rate) # print('start!') # i = 0 - for error_x,error_z in errors: + for error_x, error_z in errors: # i+= 1 # if i % 1000 == 0: - # print(f'finish {i} errors') - fail += self.Single_run(error_x,error_z) - return fail/trail - - def Get_errors(self,number,physical_error_rate=0.01,xyz_bias=[1,1,1]): - - error_random_numbers = np.random.uniform(0,1,(number,self.n))/physical_error_rate + # print(f'finish {i} errors') + fail += self.Single_run(error_x, error_z) + return fail / trail + + def Get_errors(self, number, physical_error_rate=0.01, xyz_bias=[1, 1, 1]): + + error_random_numbers = ( + np.random.uniform(0, 1, (number, self.n)) / physical_error_rate + ) xyz_sum = sum(xyz_bias) errors = [] for i in range(number): error_x = np.zeros(self.n) error_z = np.zeros(self.n) for j in range(len(error_random_numbers[i])): - if error_random_numbers[i][j]<= xyz_bias[0]/xyz_sum: + if error_random_numbers[i][j] <= xyz_bias[0] / xyz_sum: error_x[j] = 1 - elif error_random_numbers[i][j]<= (xyz_bias[0]+xyz_bias[1])/xyz_sum: + elif ( + error_random_numbers[i][j] <= (xyz_bias[0] + xyz_bias[1]) / xyz_sum + ): error_x[j] = 1 error_z[j] = 1 - elif error_random_numbers[i][j]<= 1: + elif error_random_numbers[i][j] <= 1: error_z[j] = 1 - errors.append((error_x,error_z)) + errors.append((error_x, error_z)) return errors - def init_decoder(self,physical_error_rate,bp = 'lsd'): - if bp == 'lsd': + + def init_decoder(self, physical_error_rate, bp="lsd"): + if bp == "lsd": self.bpd_z = BpLsdDecoder( self.hx, - error_rate = 2*physical_error_rate/3, + error_rate=2 * physical_error_rate / 3, channel_probs=[None], - max_iter=self.n//2, + max_iter=self.n // 2, bp_method="product_sum", - schedule = 'parallel', + schedule="parallel", lsd_method="lsd_cs", lsd_order=2, ) @@ -175,86 +190,96 @@ def init_decoder(self,physical_error_rate,bp = 'lsd'): # decoder for X-errors self.bpd_x = BpLsdDecoder( self.hz, - error_rate = 2*physical_error_rate/3, + error_rate=2 * physical_error_rate / 3, channel_probs=[None], - max_iter=self.n//2, + max_iter=self.n // 2, bp_method="product_sum", - schedule = 'parallel', + schedule="parallel", lsd_method="lsd_cs", lsd_order=2, ) else: self.bpd_z = bposd_decoder( self.hx, - error_rate=physical_error_rate/2, - channel_probs=[None], #assign error_rate to each qubit. This will override "error_rate" input variable - max_iter=self.n, #the maximum number of iterations for BP) + error_rate=physical_error_rate / 2, + channel_probs=[ + None + ], # assign error_rate to each qubit. This will override "error_rate" input variable + max_iter=self.n, # the maximum number of iterations for BP) bp_method="ms", - ms_scaling_factor=7, #min sum scaling factor. If set to zero the variable scaling factor method is used - osd_method="osd_cs", #the OSD method. Choose from: 1) "osd_e", "osd_cs", "osd0" - osd_order=7 #the osd search depth - ) + ms_scaling_factor=7, # min sum scaling factor. If set to zero the variable scaling factor method is used + osd_method="osd_cs", # the OSD method. Choose from: 1) "osd_e", "osd_cs", "osd0" + osd_order=7, # the osd search depth + ) # decoder for X-errors self.bpd_x = bposd_decoder( self.hz, - error_rate=physical_error_rate/2, - channel_probs=[None], #assign error_rate to each qubit. This will override "error_rate" input variable - max_iter=self.n, #the maximum number of iterations for BP) + error_rate=physical_error_rate / 2, + channel_probs=[ + None + ], # assign error_rate to each qubit. This will override "error_rate" input variable + max_iter=self.n, # the maximum number of iterations for BP) bp_method="ms", - ms_scaling_factor=7, #min sum scaling factor. If set to zero the variable scaling factor method is used - osd_method="osd_cs", #the OSD method. Choose from: 1) "osd_e", "osd_cs", "osd0" - osd_order=7 #the osd search depth - ) - - - - def Get_precise_logical_error_rate(self,physical_error_rate=0.01,trail=1000,block = 14): - '''using the depolorizing channel to calculate the logical error rate''' + ms_scaling_factor=7, # min sum scaling factor. If set to zero the variable scaling factor method is used + osd_method="osd_cs", # the OSD method. Choose from: 1) "osd_e", "osd_cs", "osd0" + osd_order=7, # the osd search depth + ) + + def Get_precise_logical_error_rate( + self, physical_error_rate=0.01, trail=1000, block=14 + ): + """using the depolorizing channel to calculate the logical error rate""" self.init_decoder(physical_error_rate) - - + # TODO p_l = 0 # PL = [0] - for n_e in range(1,self.n): - p_ne = binomial_probability(self.n,n_e,physical_error_rate) + for n_e in range(1, self.n): + p_ne = binomial_probability(self.n, n_e, physical_error_rate) - if n_e <= block+1 : - p_l_ne = self.P_L_given_n_e(n_e,trail) + if n_e <= block + 1: + p_l_ne = self.P_L_given_n_e(n_e, trail) # print('n_e:',n_e) else: p_l_ne = 1 - p_l += p_ne*p_l_ne + p_l += p_ne * p_l_ne # PL.append(p_l_ne) - - + return p_l - def Get_precise_logical_error_rate_iterative(self, physical_error_rate=0.01, total_trail=10000, block=999, init_samples=200, batch_size=100): + + def Get_precise_logical_error_rate_iterative( + self, + physical_error_rate=0.01, + total_trail=10000, + block=999, + init_samples=200, + batch_size=100, + ): # print('start to estimate the logical error rate') - mode = 'lsd' - self.init_decoder(physical_error_rate,bp = 'lsd') - + mode = "lsd" + self.init_decoder(physical_error_rate, bp="lsd") - strata = list(range(2, min(block + 2,self.n))) + strata = list(range(2, min(block + 2, self.n))) sample_counts = {n_e: 0 for n_e in strata} success_sums = {n_e: 0.0 for n_e in strata} - bino = {n_e: binomial_probability(self.n,n_e,physical_error_rate) for n_e in strata} + bino = { + n_e: binomial_probability(self.n, n_e, physical_error_rate) + for n_e in strata + } # print('initiallized') - for n_e in strata: # print(f'n_e: {n_e}') - if mode == 'lsd' and n_e>=50: - mode = 'osd' - self.init_decoder(physical_error_rate,bp = 'osd') + if mode == "lsd" and n_e >= 50: + mode = "osd" + self.init_decoder(physical_error_rate, bp="osd") p_l_est = self.P_L_given_n_e(n_e, init_samples) sample_counts[n_e] = init_samples success_sums[n_e] = p_l_est * init_samples - + used_samples = init_samples * len(strata) - while used_samples < total_trail: remaining = total_trail - used_samples @@ -262,35 +287,48 @@ def Get_precise_logical_error_rate_iterative(self, physical_error_rate=0.01, tot weight = {n_e: 0.0 for n_e in strata} for n_e in strata: - if success_sums[n_e]==0: - weight[n_e] = np.log(bino[n_e])+np.log(sample_counts[n_e]+current_batch-1)-3*np.log(sample_counts[n_e]+current_batch) + if success_sums[n_e] == 0: + weight[n_e] = ( + np.log(bino[n_e]) + + np.log(sample_counts[n_e] + current_batch - 1) + - 3 * np.log(sample_counts[n_e] + current_batch) + ) else: - weight[n_e] = np.log(bino[n_e])+np.log((sample_counts[n_e]-success_sums[n_e])*success_sums[n_e]*current_batch)-np.log(sample_counts[n_e]+current_batch)-3*np.log(sample_counts[n_e]) + weight[n_e] = ( + np.log(bino[n_e]) + + np.log( + (sample_counts[n_e] - success_sums[n_e]) + * success_sums[n_e] + * current_batch + ) + - np.log(sample_counts[n_e] + current_batch) + - 3 * np.log(sample_counts[n_e]) + ) # print(weight) - + max_n_e = max(weight, key=weight.get) - if mode == 'lsd' and max_n_e>=50: - mode = 'osd' - self.init_decoder(physical_error_rate,bp = 'osd') - elif mode == 'osd' and max_n_e<50: - mode = 'lsd' - self.init_decoder(physical_error_rate,bp = 'lsd') + if mode == "lsd" and max_n_e >= 50: + mode = "osd" + self.init_decoder(physical_error_rate, bp="osd") + elif mode == "osd" and max_n_e < 50: + mode = "lsd" + self.init_decoder(physical_error_rate, bp="lsd") p_l_est = self.P_L_given_n_e(max_n_e, current_batch) sample_counts[max_n_e] += current_batch success_sums[max_n_e] = current_batch * p_l_est used_samples += current_batch - p_l_total = 0 p_1 = self.get_error_rate_1() - p_l_total += binomial_probability(self.n,1,physical_error_rate)*p_1 + p_l_total += binomial_probability(self.n, 1, physical_error_rate) * p_1 # print(p_1) for n_e in strata: - p_l_total += bino[n_e] * success_sums[n_e]/sample_counts[n_e] + p_l_total += bino[n_e] * success_sums[n_e] / sample_counts[n_e] # print(sample_counts) # print({n_e: success_sums[n_e]/sample_counts[n_e] for n_e in strata}) - + return p_l_total + def get_error_rate_1(self): errors = [] for i in range(self.n): @@ -298,177 +336,195 @@ def get_error_rate_1(self): z = np.zeros(self.n) x[i] = 1 z[i] = 0 - errors.append((x,z)) + errors.append((x, z)) x = np.zeros(self.n) z = np.zeros(self.n) x[i] = 1 z[i] = 1 - errors.append((x,z)) + errors.append((x, z)) x = np.zeros(self.n) z = np.zeros(self.n) x[i] = 0 z[i] = 1 - errors.append((x,z)) + errors.append((x, z)) p = 0 for i in errors: - p+= self.Single_run(i[0],i[1]) - return p/len(errors) + p += self.Single_run(i[0], i[1]) + return p / len(errors) # def Get_logical_error_rate_through_interpolation(self,physical_error_rate=0.01,trail = 1000,block=20): # self.init_decoder(physical_error_rate) - - + # # TODO # p_l = 0 # PL = [0] # for n_e in range(1,self.n): # p_ne = binomial_probability(self.n,n_e,physical_error_rate) - # if n_e <= block+1 : + # if n_e <= block+1 : # p_l_ne = self.P_L_given_n_e(n_e,physical_error_rate,int(trail*(0.98**n_e))) # # print('n_e:',n_e) # else: # p_l_ne = 1 # p_l += p_ne*p_l_ne # PL.append(p_l_ne) - + # print(PL) # return p_l,PL - def P_L_given_n_e(self,n_e,trail=100): - '''calculate the probability of logical error given n_e error''' + def P_L_given_n_e(self, n_e, trail=100): + """calculate the probability of logical error given n_e error""" # TODO p_l_ne = 0 print(n_e) - for t in range(int(trail*(0.97**n_e))): - error_x,error_z = self.Get_error(n_e) - - p_l_ne += self.Single_run(error_x,error_z) + for t in range(int(trail * (0.97**n_e))): + error_x, error_z = self.Get_error(n_e) + + p_l_ne += self.Single_run(error_x, error_z) # print(p_l_ne/trail) - - return p_l_ne/trail + + return p_l_ne / trail + def Single_run(self, error_x, error_z): if error_z.any(): synd_z = (self.hx @ error_z) % 2 - rec_z = self.bpd_z.decode(synd_z) + rec_z = self.bpd_z.decode(synd_z) residual_z = (error_z + rec_z) % 2 else: - residual_z = error_z.copy() if error_x.any(): synd_x = (self.hz @ error_x) % 2 - rec_x = self.bpd_x.decode(synd_x) + rec_x = self.bpd_x.decode(synd_x) residual_x = (error_x + rec_x) % 2 else: residual_x = error_x.copy() - sz_cleared = ((self.hx @ residual_z) % 2 == 0).all() sx_cleared = ((self.hz @ residual_x) % 2 == 0).all() if not (sz_cleared and sx_cleared): - return 1 - logical_x_flip = ((self.lx @ residual_x) % 2).any() logical_z_flip = ((self.lz @ residual_z) % 2).any() if logical_x_flip or logical_z_flip: return 1 - return 0 - - - - def Get_error(self,d_e): - '''sample an error of weight d_e under the depolorizing model''' + def Get_error(self, d_e): + """sample an error of weight d_e under the depolorizing model""" error_x = np.zeros(self.n).astype(int) error_z = np.zeros(self.n).astype(int) - + indices = np.random.choice(self.n, d_e, replace=False) for index in indices: - error_type = np.random.choice(['x', 'z','y']) - if error_type == 'x': - error_x[ index] = 1 - elif error_type == 'z': - error_z[ index] = 1 + error_type = np.random.choice(["x", "z", "y"]) + if error_type == "x": + error_x[index] = 1 + elif error_type == "z": + error_z[index] = 1 else: - error_x[ index] = 1 - error_z[ index] = 1 + error_x[index] = 1 + error_z[index] = 1 return error_x, error_z - def _channel_update(self,update_direction): - ''' + def _channel_update(self, update_direction): + """ Function updates the channel probability vector for the second decoding component based on the first. The channel probability updates can be derived from Bayes' rule. - ''' + """ - #x component first, then z component - if update_direction=="x->z": - decoder_probs=np.zeros(self.N) + # x component first, then z component + if update_direction == "x->z": + decoder_probs = np.zeros(self.N) for i in range(self.N): - if self.bpd_x.osdw_decoding[i]==1: - if (self.channel_probs_x[i]+self.channel_probs_y[i])==0: - decoder_probs[i]=0 + if self.bpd_x.osdw_decoding[i] == 1: + if (self.channel_probs_x[i] + self.channel_probs_y[i]) == 0: + decoder_probs[i] = 0 else: - decoder_probs[i]=self.channel_probs_y[i]/(self.channel_probs_x[i]+self.channel_probs_y[i]) - elif self.bpd_x.osdw_decoding[i]==0: - decoder_probs[i]=self.channel_probs_z[i]/(1-self.channel_probs_x[i]-self.channel_probs_y[i]) - + decoder_probs[i] = self.channel_probs_y[i] / ( + self.channel_probs_x[i] + self.channel_probs_y[i] + ) + elif self.bpd_x.osdw_decoding[i] == 0: + decoder_probs[i] = self.channel_probs_z[i] / ( + 1 - self.channel_probs_x[i] - self.channel_probs_y[i] + ) + self.bpd_z.update_channel_probs(decoder_probs) - #z component first, then x component - elif update_direction=="z->x": + # z component first, then x component + elif update_direction == "z->x": self.bpd_z.osdw_decoding - decoder_probs=np.zeros(self.N) + decoder_probs = np.zeros(self.N) for i in range(self.N): - if self.bpd_z.osdw_decoding[i]==1: - - if (self.channel_probs_z[i]+self.channel_probs_y[i])==0: - decoder_probs[i]=0 + if self.bpd_z.osdw_decoding[i] == 1: + if (self.channel_probs_z[i] + self.channel_probs_y[i]) == 0: + decoder_probs[i] = 0 else: - decoder_probs[i]=self.channel_probs_y[i]/(self.channel_probs_z[i]+self.channel_probs_y[i]) - elif self.bpd_z.osdw_decoding[i]==0: - decoder_probs[i]=self.channel_probs_x[i]/(1-self.channel_probs_z[i]-self.channel_probs_y[i]) + decoder_probs[i] = self.channel_probs_y[i] / ( + self.channel_probs_z[i] + self.channel_probs_y[i] + ) + elif self.bpd_z.osdw_decoding[i] == 0: + decoder_probs[i] = self.channel_probs_x[i] / ( + 1 - self.channel_probs_z[i] - self.channel_probs_y[i] + ) - self.bpd_x.update_channel_probs(decoder_probs) - - -if __name__ == '__main__': +if __name__ == "__main__": # h = np.array([[1,1,0,0,0,0,1],[1,0,0,1,1,0,1],[0,1,1,0,1,1,0],[0,0,0,1,1,1,1]]) from parameter_converter import Parameters_converter - converter = Parameters_converter(method = 'HGP',hyperparameters={'p': 4, 'q': 4, 'm': 3}) + + converter = Parameters_converter( + method="HGP", hyperparameters={"p": 4, "q": 4, "m": 3} + ) from Construct_Code import CSS from Evaluate import CSS_Evaluator + code1 = np.array([1, 3, 0, 3, 2, 0, 1, 2, 2, 1, 3, 0, 0, 1, 0, 3]) code1 = converter.generate_parameters(code1) css_instance_1 = CSS() - css_instance_1.construct(method='HGP',para_dict=code1) + css_instance_1.construct(method="HGP", para_dict=code1) import pyldpc - h, _ = pyldpc.make_ldpc(6, 2,3) + h, _ = pyldpc.make_ldpc(6, 2, 3) # print(h) - h =np.array ([[ 1,1,1,1,1,1,0,0,0,0,0,0],[ 0,0,0,0,0,0,1,1,1,1,1,1]]) - surface_code = hgp(h,h) + h = np.array( + [[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]] + ) + surface_code = hgp(h, h) Hx = surface_code.hx Hz = surface_code.hz - Hx = np.array([[1,1,1,0,0,0,1,0],[0,0,1,0,1,1,1,0],[1,1,0,1,0,0,0,1],[0,0,0,1,1,1,0,1]]) - Hz = np.array([[0,1,0,0,1,0,1,1],[0,1,1,1,1,0,0,0],[1,0,0,0,0,1,1,1],[1,0,1,1,0,1,0,0]]) + Hx = np.array( + [ + [1, 1, 1, 0, 0, 0, 1, 0], + [0, 0, 1, 0, 1, 1, 1, 0], + [1, 1, 0, 1, 0, 0, 0, 1], + [0, 0, 0, 1, 1, 1, 0, 1], + ] + ) + Hz = np.array( + [ + [0, 1, 0, 0, 1, 0, 1, 1], + [0, 1, 1, 1, 1, 0, 0, 0], + [1, 0, 0, 0, 0, 1, 1, 1], + [1, 0, 1, 1, 0, 1, 0, 0], + ] + ) Hx = css_instance_1.HX Hz = css_instance_1.HZ - CSS_evaluator = CSS_Evaluator(Hx,Hz) + CSS_evaluator = CSS_Evaluator(Hx, Hz) returns = CSS_evaluator.Get_error_rate(physical_error_rate=0.02) - return2 = CSS_evaluator.Get_precise_logical_error_rate(physical_error_rate=0.02,trail=300,block=10) + return2 = CSS_evaluator.Get_precise_logical_error_rate( + physical_error_rate=0.02, trail=300, block=10 + ) print(returns) print(return2) # print(type(returns)) @@ -481,23 +537,29 @@ def _channel_update(self,update_direction): # # plt.legend() # plt.grid(True) - PP = np.linspace(0.5, 7, 15) - PP = 10**(-PP) - print(f'PP:{PP}') - p_l_acc = np.array([CSS_evaluator.Get_precise_logical_error_rate(physical_error_rate=p,trail=1000,block=10) for p in PP]) + PP = 10 ** (-PP) + print(f"PP:{PP}") + p_l_acc = np.array( + [ + CSS_evaluator.Get_precise_logical_error_rate( + physical_error_rate=p, trail=1000, block=10 + ) + for p in PP + ] + ) p_l = np.array([CSS_evaluator.Get_error_rate(physical_error_rate=p) for p in PP]) - print(f'PP:{PP}') - print(f'p_l_acc:{p_l_acc}') - print(f'p_l:{p_l}') - plt.xscale('log') - - plt.yscale('log') - plt.plot(PP, p_l, label='simple Monte Carlo', color='g') - plt.plot(PP, p_l_acc, label='Precise', color='r') - plt.xlabel('PP') - plt.ylabel('P_l') - plt.title('Two estimates of P_l') + print(f"PP:{PP}") + print(f"p_l_acc:{p_l_acc}") + print(f"p_l:{p_l}") + plt.xscale("log") + + plt.yscale("log") + plt.plot(PP, p_l, label="simple Monte Carlo", color="g") + plt.plot(PP, p_l_acc, label="Precise", color="r") + plt.xlabel("PP") + plt.ylabel("P_l") + plt.title("Two estimates of P_l") plt.legend() plt.grid(True) - plt.show() \ No newline at end of file + plt.show() diff --git a/evolutionary_algorithm/ea.py b/evolutionary_algorithm/ea.py index bf070dc..1d920c8 100644 --- a/evolutionary_algorithm/ea.py +++ b/evolutionary_algorithm/ea.py @@ -1,5 +1,5 @@ """ - This file contains the implementation of the Evolutionary Algorithm on the QEC codes. +This file contains the implementation of the Evolutionary Algorithm on the QEC codes. """ from pymoo.core.problem import Problem @@ -7,14 +7,15 @@ class CanonicalCSSEvolutionaryOptimization(Problem): - def __init__(self,nbits): - super().__init__(n_var=nbits, - n_obj=1, - n_constr=0, - xl=np.zeros(nbits), - xu=np.ones(nbits), - type_var=int) - + def __init__(self, nbits): + super().__init__( + n_var=nbits, + n_obj=1, + n_constr=0, + xl=np.zeros(nbits), + xu=np.ones(nbits), + type_var=int, + ) def _evaluate(self, x, out, *args, **kwargs): # calculate the ebaluation function @@ -22,29 +23,29 @@ def _evaluate(self, x, out, *args, **kwargs): out["F"] = f - class BivariateBicycleCodeEvolutionaryOptimization(Problem): - def __init__(self,l,m,obj_func): + def __init__(self, l, m, obj_func): self.objective_function = obj_func self.evaluation_history = [] self.best_result = 1 self.best_parameters = [] - super().__init__(n_var=2*(l+m-1), - n_obj=1, - n_constr=0, - xl=np.zeros(2*(l+m-1)), - xu=np.ones(2*(l+m-1)), - type_var=int) - + super().__init__( + n_var=2 * (l + m - 1), + n_obj=1, + n_constr=0, + xl=np.zeros(2 * (l + m - 1)), + xu=np.ones(2 * (l + m - 1)), + type_var=int, + ) def _evaluate(self, x, out, *args, **kwargs): # calculate the ebaluation function out_list = [] for i in x: # stabilizer_code = self.constructor.construct(i) - temp,_ = self.objective_function.forward(i) + temp, _ = self.objective_function.forward(i) temp = -temp - if self.best_result>= temp: + if self.best_result >= temp: self.best_parameters = i self.best_result = temp out_list.append(temp) diff --git a/initial_points.py b/initial_points.py index a7beca1..823b385 100644 --- a/initial_points.py +++ b/initial_points.py @@ -13,84 +13,98 @@ def set_all_seeds(seed: int = 42): np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed(seed) - torch.cuda.manual_seed_all(seed) + torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False - -class Get_new_points_function(): - def __init__(self,method='qc-ldpc-hgp',hyperparameters = {'p': 2, 'q': 6, 'm': 2},encode='None'): + +class Get_new_points_function: + def __init__( + self, + method="qc-ldpc-hgp", + hyperparameters={"p": 2, "q": 6, "m": 2}, + encode="None", + ): self.method = method self.hyperparameters = hyperparameters self.encode = encode self.init = False - def get_new_points_function(self,number): - if self.method == 'qc-ldpc-hgp': + def get_new_points_function(self, number): + if self.method == "qc-ldpc-hgp": new_points = self.get_new_points_HGP(number) - elif self.method == 'bb': + elif self.method == "bb": new_points = self.get_new_bb_vector(number) return new_points - - def get_new_points_HGP(self,number): - return np.random.randint(0, self.hyperparameters['m'] + 1, (number, self.hyperparameters['p'] * self.hyperparameters['q'])) - def get_new_bb_vector(self,number): + def get_new_points_HGP(self, number): + return np.random.randint( + 0, + self.hyperparameters["m"] + 1, + (number, self.hyperparameters["p"] * self.hyperparameters["q"]), + ) + + def get_new_bb_vector(self, number): results = [] - l = self.hyperparameters['l'] - g = self.hyperparameters['g'] - if self.init == False and l==12 and g==999: - print('best known bb code added to initial points') - self.init=True + l = self.hyperparameters["l"] + g = self.hyperparameters["g"] + if self.init == False and l == 12 and g == 999: + print("best known bb code added to initial points") + self.init = True - a = np.zeros((l+g-1)*2) - a[3]=1 - a[11+1]=1 - a[11+2]=1 - a[17+1]=1 - a[17+2]=1 - a[17+11+3]=1 + a = np.zeros((l + g - 1) * 2) + a[3] = 1 + a[11 + 1] = 1 + a[11 + 2] = 1 + a[17 + 1] = 1 + a[17 + 2] = 1 + a[17 + 11 + 3] = 1 results.append(a) - while number>0: - new_point = np.random.randint(0,2, size=(l+g-1)*2) + while number > 0: + new_point = np.random.randint(0, 2, size=(l + g - 1) * 2) c = code_constructor.construct(new_point) - if c.k==0: + if c.k == 0: continue else: results.append(new_point) number -= 1 return np.array(results) -if __name__ == '__main__': + + +if __name__ == "__main__": import pickle + seed = 42 set_all_seeds(seed) l = 12 - g = 6 # g here is m in Bravyi et al's paper - para_dict = {'l':l,'g':g} - code_class = 'bb' + g = 6 # g here is m in Bravyi et al's paper + para_dict = {"l": l, "g": g} + code_class = "bb" - - code_constructor = CodeConstructor(method=code_class,para_dict = para_dict) + code_constructor = CodeConstructor(method=code_class, para_dict=para_dict) # define objective function - pp=0.05 - Obj_Func = ObjectiveFunction(code_constructor, pp=pp,decoder_param={'trail':10_000}) + pp = 0.05 + Obj_Func = ObjectiveFunction( + code_constructor, pp=pp, decoder_param={"trail": 10_000} + ) obj_func = Obj_Func.forward pl_to_obj = Obj_Func.pl_to_obj_with_std # method of sampling new points - gnp = Get_new_points_function(method=code_class,hyperparameters = para_dict).get_new_points_function + gnp = Get_new_points_function( + method=code_class, hyperparameters=para_dict + ).get_new_points_function init_num = 20 for i in range(5): - print(f'generating new points...round {i}') + print(f"generating new points...round {i}") X_init = gnp(init_num) y_init = [] pl_init = [] for x in X_init: - - y,pl = obj_func(x) + y, pl = obj_func(x) y_init.append(y) pl_init.append(pl) - print(f'x:{x},y:{y},pl:{pl}') - data = {'X':X_init,'y':y_init,'pl':pl_init} - with open(f"./data/BO_initial_points/BO_initial_points_{i+5}.pkl", "wb") as f: - pickle.dump(data, f) \ No newline at end of file + print(f"x:{x},y:{y},pl:{pl}") + data = {"X": X_init, "y": y_init, "pl": pl_init} + with open(f"./data/BO_initial_points/BO_initial_points_{i + 5}.pkl", "wb") as f: + pickle.dump(data, f) diff --git a/readme.ipynb b/readme.ipynb index 77ef024..721980f 100644 --- a/readme.ipynb +++ b/readme.ipynb @@ -8,7 +8,7 @@ "outputs": [], "source": [ "# Some tools for writing this file:\n", - "line = '**********************************************************\\n'\n", + "line = \"**********************************************************\\n\"\n", "import numpy as np" ] }, @@ -38,11 +38,11 @@ "from code_construction.code_construction import CodeConstructor\n", "\n", "# An example of rotated surface code\n", - "surface_code_constructor = CodeConstructor(method = 'rotated-surface')\n", + "surface_code_constructor = CodeConstructor(method=\"rotated-surface\")\n", "distance3 = surface_code_constructor.construct(3)\n", - "print(f'{line}3x3 rotated surface code,n:{distance3.n},k:{distance3.k}\\n{line}')\n", - "# this function take odd integers (p>=3) as input \n", - "'''\n", + "print(f\"{line}3x3 rotated surface code,n:{distance3.n},k:{distance3.k}\\n{line}\")\n", + "# this function take odd integers (p>=3) as input\n", + "\"\"\"\n", " returns a CSSCode instance, which has:\n", " distance3.hx (np.ndarray), # X stabilizers\n", " distance3.hz (np.ndarray), # Z stabilizers\n", @@ -50,28 +50,60 @@ " distance3.k (int), # number of logical qubits\n", " distance3.lx (np.ndarray), # X logical operators\n", " distance3.lz (np.ndarray) # Z logical operators\n", - "'''\n", + "\"\"\"\n", "\n", "# An example of hypergraph product code\n", - "hgp_code_constructor = CodeConstructor(method = 'qc-ldpc-hgp',para_dict = {\n", - " 'p':3,\n", - " 'q':3,\n", - " 'm':4\n", - "})\n", - "M = np.array([0,2,1,2,1,3,3,3,1]) # just a random (flatten) matrix in Z_5(m+1)^{3(p) \\times 3(q)}, see the definition CodeConstructor.qc_ldpc_hgp_construction()\n", + "hgp_code_constructor = CodeConstructor(\n", + " method=\"qc-ldpc-hgp\", para_dict={\"p\": 3, \"q\": 3, \"m\": 4}\n", + ")\n", + "M = np.array(\n", + " [0, 2, 1, 2, 1, 3, 3, 3, 1]\n", + ") # just a random (flatten) matrix in Z_5(m+1)^{3(p) \\times 3(q)}, see the definition CodeConstructor.qc_ldpc_hgp_construction()\n", "random_hgp_code = hgp_code_constructor.construct(M)\n", - "print(f'{line}A random hypergraph product code,n:{random_hgp_code.n},k:{random_hgp_code.k};\\nThe weight of each row of Hx,Hz is upper bounded by 2*p = 6, the weight of each column of Hx,Hz is upper bounded by 2*q=6;\\n n=m(p+q)\\n{line}')\n", + "print(\n", + " f\"{line}A random hypergraph product code,n:{random_hgp_code.n},k:{random_hgp_code.k};\\nThe weight of each row of Hx,Hz is upper bounded by 2*p = 6, the weight of each column of Hx,Hz is upper bounded by 2*q=6;\\n n=m(p+q)\\n{line}\"\n", + ")\n", "\n", "# An example of bivariate bicycle code\n", - "bb_code_constructor = CodeConstructor(method = 'bb',para_dict = {\n", - " 'l':12,\n", - " 'g':6\n", - "})\n", + "bb_code_constructor = CodeConstructor(method=\"bb\", para_dict={\"l\": 12, \"g\": 6})\n", "gross_parameter = [\n", - " 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0.\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", "]\n", "gross_code = bb_code_constructor.construct(gross_parameter)\n", - "print(f'{line}The [144,12,12] gross code,n:{gross_code.n},k:{gross_code.k}\\n{line}')" + "print(f\"{line}The [144,12,12] gross code,n:{gross_code.n},k:{gross_code.k}\\n{line}\")" ] }, { @@ -126,11 +158,10 @@ "# An example of calculating logical error rate:\n", "\n", "from evaluation.decoder_based_evaluation import CSS_Evaluator\n", + "\n", "evaluator = CSS_Evaluator(gross_code.hx, gross_code.hz)\n", "pL = evaluator.Get_logical_error_rate_Monte_Carlo(\n", - " physical_error_rate=0.05,\n", - " xyz_bias=[1, 1, 1],\n", - " trail=10_000\n", + " physical_error_rate=0.05, xyz_bias=[1, 1, 1], trail=10_000\n", ")\n", "print(f\"gross code's logical error rate:{pL}, where physical error rate = 0.05\")" ] @@ -229,12 +260,23 @@ "metadata": {}, "outputs": [], "source": [ - "from evaluation.circuit_level_noise import MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise\n", + "from evaluation.circuit_level_noise import (\n", + " MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise,\n", + ")\n", "\n", "\n", - "mc = MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise(gross_code,noise_model='SD6',p=0.007,rounds=12,custom_error_model={},decoder='bplsd')\n", - "pl = mc.run(shots= 100_000, max_error = 1000, num_workers = 24)\n", - "print(f'the logical error rate under SD6 noise model with physical error rate = 0.007 is :{pl}')" + "mc = MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise(\n", + " gross_code,\n", + " noise_model=\"SD6\",\n", + " p=0.007,\n", + " rounds=12,\n", + " custom_error_model={},\n", + " decoder=\"bplsd\",\n", + ")\n", + "pl = mc.run(shots=100_000, max_error=1000, num_workers=24)\n", + "print(\n", + " f\"the logical error rate under SD6 noise model with physical error rate = 0.007 is :{pl}\"\n", + ")" ] }, { @@ -255,26 +297,27 @@ "outputs": [], "source": [ "from dataset import QEC_Dataset\n", + "\n", "l = 12\n", "g = 6\n", "dataset = QEC_Dataset(\n", - " l=l,\n", - " g=g,\n", - " load = False,\n", - " number = 100,\n", - " save = True,\n", - " path='./data/codes',\n", - " p=0.007,\n", - " noise_model='SD6',\n", - " noise_and_decoder_param={\n", - " 'rounds':12,\n", - " 'decoder':'bplsd',\n", - " 'trail':100_000,\n", - " 'max_error':100,\n", - " 'num_worker':24\n", - " },\n", - " )\n", - "# this is used to generate the dataset; \n", + " l=l,\n", + " g=g,\n", + " load=False,\n", + " number=100,\n", + " save=True,\n", + " path=\"./data/codes\",\n", + " p=0.007,\n", + " noise_model=\"SD6\",\n", + " noise_and_decoder_param={\n", + " \"rounds\": 12,\n", + " \"decoder\": \"bplsd\",\n", + " \"trail\": 100_000,\n", + " \"max_error\": 100,\n", + " \"num_worker\": 24,\n", + " },\n", + ")\n", + "# this is used to generate the dataset;\n", "# you can selet l,g as you want(By this way you can only generate bb codes; if you want to generate other codes, please change the definition of QEC_Dataset in dataset.py)" ] }, @@ -288,28 +331,38 @@ "import torch\n", "from dataset import QEC_Dataset\n", "import matplotlib.pyplot as plt\n", + "\n", "# delolarizing noise, physial error rate = 0.05\n", "l = 6\n", "g = 3\n", - "para_dict = {'l':l,'g':g}\n", - "total_dataset = QEC_Dataset(l,g,load = True,number = 300,save = True,path='./data/codes/')\n", + "para_dict = {\"l\": l, \"g\": g}\n", + "total_dataset = QEC_Dataset(\n", + " l, g, load=True, number=300, save=True, path=\"./data/codes/\"\n", + ")\n", "total_dataset.X = np.array(total_dataset.X)\n", "\n", + "\n", "# My current dataset uses word error rate: WER = 1-(1-LER)^{1/k}\n", - "def convert_to_logical_error_rate(x,y):\n", - " codeconstructor = CodeConstructor(method='bb',para_dict = para_dict)\n", + "def convert_to_logical_error_rate(x, y):\n", + " codeconstructor = CodeConstructor(method=\"bb\", para_dict=para_dict)\n", " x1 = codeconstructor.construct(x)\n", " # print(x.k)\n", - " y = 1 - (1 - y)**x1.k\n", + " y = 1 - (1 - y) ** x1.k\n", " return y\n", + "\n", + "\n", "# set word error rate(logical error rate per qubit) to logical error rate\n", "# print(total_dataset.y)\n", - "total_dataset.y = torch.tensor([convert_to_logical_error_rate(total_dataset.X[i],total_dataset.y[i]) for i in range(len(total_dataset.y))])\n", + "total_dataset.y = torch.tensor(\n", + " [\n", + " convert_to_logical_error_rate(total_dataset.X[i], total_dataset.y[i])\n", + " for i in range(len(total_dataset.y))\n", + " ]\n", + ")\n", "# print(total_dataset.y)\n", "\n", "\n", - "\n", - "eps = 1e-8 \n", + "eps = 1e-8\n", "total_dataset.y = np.log(total_dataset.y + eps)\n", "\n", "\"\"\"\n", @@ -321,15 +374,17 @@ "\n", "def plot_distribution(y, bin_width):\n", " bins = np.arange(np.min(y), np.max(y) + bin_width, bin_width)\n", - " \n", + "\n", " plt.figure(figsize=(8, 5))\n", - " plt.hist(y, bins=bins, edgecolor='black')\n", - " plt.xlabel('value')\n", - " plt.ylabel('frequency')\n", - " plt.title(f'distribution of y with width={bin_width}')\n", - " plt.grid(axis='y', alpha=0.3)\n", + " plt.hist(y, bins=bins, edgecolor=\"black\")\n", + " plt.xlabel(\"value\")\n", + " plt.ylabel(\"frequency\")\n", + " plt.title(f\"distribution of y with width={bin_width}\")\n", + " plt.grid(axis=\"y\", alpha=0.3)\n", " plt.tight_layout()\n", " plt.show()\n", + "\n", + "\n", "plot_distribution(total_dataset.y.detach().cpu().numpy(), bin_width=0.1)" ] }, @@ -342,28 +397,39 @@ "source": [ "import torch\n", "from dataset import QEC_Dataset\n", + "\n", "# SD6 noise, physical error rate = 0.007\n", "l = 12\n", "g = 6\n", - "para_dict = {'l':l,'g':g}\n", - "total_dataset = QEC_Dataset(l,g,load = True,number = 300,save = True,path='./data/codes/')\n", + "para_dict = {\"l\": l, \"g\": g}\n", + "total_dataset = QEC_Dataset(\n", + " l, g, load=True, number=300, save=True, path=\"./data/codes/\"\n", + ")\n", "total_dataset.X = np.array(total_dataset.X)\n", "\n", + "\n", "# My current dataset uses word error rate: WER = 1-(1-LER)^{1/k}\n", - "def convert_to_logical_error_rate(x,y):\n", - " codeconstructor = CodeConstructor(method='bb',para_dict = para_dict)\n", + "def convert_to_logical_error_rate(x, y):\n", + " codeconstructor = CodeConstructor(method=\"bb\", para_dict=para_dict)\n", " x1 = codeconstructor.construct(x)\n", " # print(x.k)\n", - " y = 1 - (1 - y)**x1.k\n", + " y = 1 - (1 - y) ** x1.k\n", " return y\n", + "\n", + "\n", "# set word error rate(logical error rate per qubit) to logical error rate\n", "# print(total_dataset.y)\n", - "total_dataset.y = torch.tensor([convert_to_logical_error_rate(total_dataset.X[i],total_dataset.y[i]) for i in range(len(total_dataset.y))])\n", + "total_dataset.y = torch.tensor(\n", + " [\n", + " convert_to_logical_error_rate(total_dataset.X[i], total_dataset.y[i])\n", + " for i in range(len(total_dataset.y))\n", + " ]\n", + ")\n", "# print(total_dataset.y)\n", "\n", "\n", "print(sorted(total_dataset.y))\n", - "eps = 1e-8 \n", + "eps = 1e-8\n", "total_dataset.y = np.log(total_dataset.y + eps)\n", "\n", "\"\"\"\n", @@ -375,17 +441,19 @@ "\n", "def plot_distribution(y, bin_width):\n", " bins = np.arange(np.min(y), np.max(y) + bin_width, bin_width)\n", - " \n", + "\n", " plt.figure(figsize=(8, 5))\n", - " plt.hist(y, bins=bins, edgecolor='black')\n", - " plt.xlabel('value')\n", - " plt.ylabel('frequency')\n", - " plt.title(f'distribution of y with width={bin_width}')\n", - " plt.grid(axis='y', alpha=0.3)\n", + " plt.hist(y, bins=bins, edgecolor=\"black\")\n", + " plt.xlabel(\"value\")\n", + " plt.ylabel(\"frequency\")\n", + " plt.title(f\"distribution of y with width={bin_width}\")\n", + " plt.grid(axis=\"y\", alpha=0.3)\n", " plt.tight_layout()\n", " # plt.xscale('log')\n", " plt.show()\n", - "plot_distribution(total_dataset.y.detach().cpu().numpy(), bin_width=0.1)\n" + "\n", + "\n", + "plot_distribution(total_dataset.y.detach().cpu().numpy(), bin_width=0.1)" ] } ], diff --git a/readme2.ipynb b/readme2.ipynb index 033941f..35660e4 100644 --- a/readme2.ipynb +++ b/readme2.ipynb @@ -70,17 +70,13 @@ "metadata": {}, "outputs": [], "source": [ - "\n", "!pip install torch==2.6.0 \n", "!pip install torch-geometric\n", "!pip install gpytorch botorch ax pymatching pymoo qecsim qldpc\n", "!pip install toponetx topomodelx topoembedx\n", "!pip install stim sinter\n", "!pip install ldpc bposd\n", - "!pip install pymoo\n", - "\n", - "\n", - "\n" + "!pip install pymoo" ] }, { @@ -103,8 +99,7 @@ "from bayesian_optimization.objective_function import ObjectiveFunction\n", "from bayesian_optimization.encoder import *\n", "from bayesian_optimization.chaincomplexembedding import *\n", - "from bayesian_optimization.gp import *\n", - "\n" + "from bayesian_optimization.gp import *" ] }, { @@ -151,24 +146,26 @@ "outputs": [], "source": [ "import pickle\n", - "l=12\n", - "g=6\n", + "\n", + "l = 12\n", + "g = 6\n", "dataset_index = 0\n", "seed = 1\n", - "filename = f'./data/BO_results/BO_{l}_{g}_{dataset_index}_{seed}.pkl' # if you want to read the EA/RS results, place replace the prefix 'BO' to 'EA' or 'RS'\n", + "filename = f\"./data/BO_results/BO_{l}_{g}_{dataset_index}_{seed}.pkl\" # if you want to read the EA/RS results, place replace the prefix 'BO' to 'EA' or 'RS'\n", "\n", - "best_known_y = -0.00693 # The objective function of [[144,26]] code. \n", - "with open(filename,'rs') as f:\n", + "best_known_y = -0.00693 # The objective function of [[144,26]] code.\n", + "with open(filename, \"rs\") as f:\n", " data = pickle.load(f)\n", - " flat = data['evaluation_history'] # this is used to plot the best-so-far curve\n", - " best_y = data['best_y'] # if this is better than the best know found code, we could replace the results in figure 7 and 9. \n", - " best_x = data['best_x']\n", + " flat = data[\"evaluation_history\"] # this is used to plot the best-so-far curve\n", + " best_y = data[\n", + " \"best_y\"\n", + " ] # if this is better than the best know found code, we could replace the results in figure 7 and 9.\n", + " best_x = data[\"best_x\"]\n", " if best_y >= best_known_y:\n", - " codeconstructor = CodeConstructor(method='bb',para_dict={'l':l,'g':g})\n", + " codeconstructor = CodeConstructor(method=\"bb\", para_dict={\"l\": l, \"g\": g})\n", " c = codeconstructor.construct(best_x)\n", " k = c.k\n", - " print(f'This [[144,{k}]] code is better')\n", - " " + " print(f\"This [[144,{k}]] code is better\")" ] }, { @@ -200,11 +197,55 @@ "metadata": {}, "outputs": [], "source": [ - "pp_list = [0.05, 0.040036870145840404, 0.032059019421497734, 0.0256708559516296, 0.020555614525359374, 0.01645964939039528, 0.013179856905786339, 0.010553604389554513, 0.008450665770303305, 0.0067667641618306355]\n", + "pp_list = [\n", + " 0.05,\n", + " 0.040036870145840404,\n", + " 0.032059019421497734,\n", + " 0.0256708559516296,\n", + " 0.020555614525359374,\n", + " 0.01645964939039528,\n", + " 0.013179856905786339,\n", + " 0.010553604389554513,\n", + " 0.008450665770303305,\n", + " 0.0067667641618306355,\n", + "]\n", "# [144,26]\n", - "code_144_26= [0.011445891946481712, 0.005810775130175694, 0.0028562211382342495, 0.0015016991653288292, 0.0007955535034166461, 0.00042144348451766955, 0.00023259809361952932, 0.00016032087308970322, 0.00012056614268329824, 4.5025331880887975e-05]\n", - "gross = [0.004941267916952596, 0.002594195977199232, 0.001366896217540714, 0.0007455495580136473, 0.0003992086896752456, 0.00020857243058891584, 9.588388262971037e-05, 5.585048616230104e-05, 2.5837004546613862e-05, 2.083572086741814e-05]\n", - "code_72_12_6=[0.010151361651358104, 0.0050821776826791565, 0.0025933384934497816, 0.0013330638587575327, 0.0006506567387046802, 0.00036238808305733006, 0.00018184843515745008, 0.00011340403922088793, 5.5016644518901536e-05, 1.8335182204287648e-05]" + "code_144_26 = [\n", + " 0.011445891946481712,\n", + " 0.005810775130175694,\n", + " 0.0028562211382342495,\n", + " 0.0015016991653288292,\n", + " 0.0007955535034166461,\n", + " 0.00042144348451766955,\n", + " 0.00023259809361952932,\n", + " 0.00016032087308970322,\n", + " 0.00012056614268329824,\n", + " 4.5025331880887975e-05,\n", + "]\n", + "gross = [\n", + " 0.004941267916952596,\n", + " 0.002594195977199232,\n", + " 0.001366896217540714,\n", + " 0.0007455495580136473,\n", + " 0.0003992086896752456,\n", + " 0.00020857243058891584,\n", + " 9.588388262971037e-05,\n", + " 5.585048616230104e-05,\n", + " 2.5837004546613862e-05,\n", + " 2.083572086741814e-05,\n", + "]\n", + "code_72_12_6 = [\n", + " 0.010151361651358104,\n", + " 0.0050821776826791565,\n", + " 0.0025933384934497816,\n", + " 0.0013330638587575327,\n", + " 0.0006506567387046802,\n", + " 0.00036238808305733006,\n", + " 0.00018184843515745008,\n", + " 0.00011340403922088793,\n", + " 5.5016644518901536e-05,\n", + " 1.8335182204287648e-05,\n", + "]" ] }, { @@ -228,15 +269,49 @@ "metadata": {}, "outputs": [], "source": [ - "pp_list = [0.07, 0.05605161820417657, 0.04488262719009683, 0.035939198332281444, 0.028777860335503124, 0.02304350914655339, 0.018451799668100875, 0.014775046145376319, 0.011830932078424629, 0.00947346982656289]\n", - "gross = [0.01468388594008141, 0.00730116936108316, 0.003624729974722629, 0.001908233950801752, 0.0010434677566117934, 0.0005491556129465502, 0.00029966005248871497, 0.00017099405602716278, 6.835902874036126e-05, 4.584489121428614e-05]\n", - "code_144_8 = [0.01986802811443611, 0.00698988488192942, 0.0023225422652949357, 0.0007444367661358919, 0.0002677507823637404, 6.50147923076938e-05, 3.2503697475871896e-05, 1.125044299365996e-05, 8.75026798052847e-06, 2.5000218752957437e-06]\n", + "pp_list = [\n", + " 0.07,\n", + " 0.05605161820417657,\n", + " 0.04488262719009683,\n", + " 0.035939198332281444,\n", + " 0.028777860335503124,\n", + " 0.02304350914655339,\n", + " 0.018451799668100875,\n", + " 0.014775046145376319,\n", + " 0.011830932078424629,\n", + " 0.00947346982656289,\n", + "]\n", + "gross = [\n", + " 0.01468388594008141,\n", + " 0.00730116936108316,\n", + " 0.003624729974722629,\n", + " 0.001908233950801752,\n", + " 0.0010434677566117934,\n", + " 0.0005491556129465502,\n", + " 0.00029966005248871497,\n", + " 0.00017099405602716278,\n", + " 6.835902874036126e-05,\n", + " 4.584489121428614e-05,\n", + "]\n", + "code_144_8 = [\n", + " 0.01986802811443611,\n", + " 0.00698988488192942,\n", + " 0.0023225422652949357,\n", + " 0.0007444367661358919,\n", + " 0.0002677507823637404,\n", + " 6.50147923076938e-05,\n", + " 3.2503697475871896e-05,\n", + " 1.125044299365996e-05,\n", + " 8.75026798052847e-06,\n", + " 2.5000218752957437e-06,\n", + "]\n", "# These data was calculated from 100_000 Monte-Carlo decodings, the standard error will be sqrt(wer(1-wer)/100_000)\n", "import matplotlib.pyplot as plt\n", - "plt.plot(pp_list,gross,label='gross')\n", - "plt.plot(pp_list,code_144_8,label='144,8')\n", - "plt.yscale('log')\n", - "plt.xscale('log')\n", + "\n", + "plt.plot(pp_list, gross, label=\"gross\")\n", + "plt.plot(pp_list, code_144_8, label=\"144,8\")\n", + "plt.yscale(\"log\")\n", + "plt.xscale(\"log\")\n", "plt.legend()\n", "plt.show()" ] @@ -262,19 +337,56 @@ "source": [ "l = 12\n", "g = 6\n", - "para_dict = {'l':l,'g':g}\n", - "code_class = 'bb'\n", + "para_dict = {\"l\": l, \"g\": g}\n", + "code_class = \"bb\"\n", "from code_construction.code_construction import CodeConstructor\n", - "code_constructor = CodeConstructor(method=code_class,para_dict = para_dict)\n", - "x = [0., 1., 1., 0., 1., 1., 0., 1., 0., 0., 0., 1., 0., 0., 0., 1., 1., 1.,\n", - " 1., 1., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 1., 0., 0., 1.] # Let x be the best_x we have\n", + "\n", + "code_constructor = CodeConstructor(method=code_class, para_dict=para_dict)\n", + "x = [\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 1.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + "] # Let x be the best_x we have\n", "c = code_constructor.construct(x)\n", - "psuedo_t = ObjectiveFunction(code_constructor, pp=pp,decoder_param={'trail': 100000}).psuedo_t\n", + "psuedo_t = ObjectiveFunction(\n", + " code_constructor, pp=pp, decoder_param={\"trail\": 100000}\n", + ").psuedo_t\n", "\n", "# this function is used to calculate psuedo_t\n", "print(psuedo_t(c))\n", "# code rate k/n\n", - "print(c.k/144)" + "print(c.k / 144)" ] }, { @@ -292,9 +404,17 @@ "# 11*11surface code, psuedo t:12.741790221308744\n", "# 13*13surface code, psuedo t:17.228540816991853\n", "# 15*15surface code, psuedo t:22.092362338524666\n", - "dis = [3,5,7,9,11,13,15]\n", - "code_rates = [1/i**2 for i in dis]\n", - "surfacecodes_psuedot = [1.240409037092388,3.168288067483846,5.806713473577745,9.007246610022946,12.741790221308744,17.228540816991853,22.092362338524666]\n", + "dis = [3, 5, 7, 9, 11, 13, 15]\n", + "code_rates = [1 / i**2 for i in dis]\n", + "surfacecodes_psuedot = [\n", + " 1.240409037092388,\n", + " 3.168288067483846,\n", + " 5.806713473577745,\n", + " 9.007246610022946,\n", + " 12.741790221308744,\n", + " 17.228540816991853,\n", + " 22.092362338524666,\n", + "]\n", "\n", "# plt.scatter([26/144],[8.280255381569452/144],color='#f77a66',label='[[144,26]] bb code we discoverd')\n", "# plt.scatter([8/144],[11.450956649650138/144],color=\"#f7f766\",label='[[144,8]] bb code we discoverd')\n", @@ -313,32 +433,39 @@ "# This is used to plot the non-degenerate Hamming bound in the psuedo_t vs code rate view.\n", "from scipy.special import comb\n", "import matplotlib.pyplot as plt\n", - "def binomial_probability(n,d_e,p_p):\n", - " return comb(n, d_e) * (p_p ** d_e) * ((1 - p_p) ** (n - d_e))\n", - "def code_rate_delta(t,n = 144):\n", + "\n", + "\n", + "def binomial_probability(n, d_e, p_p):\n", + " return comb(n, d_e) * (p_p**d_e) * ((1 - p_p) ** (n - d_e))\n", + "\n", + "\n", + "def code_rate_delta(t, n=144):\n", " # t = int(delta*n / 2)\n", " t = int(t)\n", - " delta = (2*t+1)\n", + " delta = 2 * t + 1\n", " s = 0\n", " for i in range(t):\n", - " s += comb(n, i)* (3**i)\n", + " s += comb(n, i) * (3**i)\n", " k = np.floor(n - np.log2(s))\n", - " \n", - " CR = k/n\n", + "\n", + " CR = k / n\n", "\n", " return CR\n", "\n", + "\n", "# select n here; I chose 144,72 in my thesis\n", - "for n in [144,72,36,288,576]:\n", + "for n in [144, 72, 36, 288, 576]:\n", " CR = []\n", " Delta = []\n", - " for t in range(1,n//2):\n", - " cr = code_rate_delta(t,n)\n", - " if cr>=0 and cr<=0.8:\n", + " for t in range(1, n // 2):\n", + " cr = code_rate_delta(t, n)\n", + " if cr >= 0 and cr <= 0.8:\n", " CR.append(cr)\n", - " Delta.append(t/n)\n", - " plt.plot(CR,Delta,label=f'n={n} Non-degenerate CSS Code Hamming Bound')\n", - " print(f'n={n} non-degenerate CSS Hamming bound: code rates:{CR}, psuedo t//n:{Delta}')\n", + " Delta.append(t / n)\n", + " plt.plot(CR, Delta, label=f\"n={n} Non-degenerate CSS Code Hamming Bound\")\n", + " print(\n", + " f\"n={n} non-degenerate CSS Hamming bound: code rates:{CR}, psuedo t//n:{Delta}\"\n", + " )\n", "plt.show()" ] } diff --git a/test_bo.ipynb b/test_bo.ipynb index a538f82..c23b761 100644 --- a/test_bo.ipynb +++ b/test_bo.ipynb @@ -7,8 +7,8 @@ "metadata": {}, "outputs": [], "source": [ - "device = 'cuda'\n", - "DEVICE = 'cuda'" + "device = \"cuda\"\n", + "DEVICE = \"cuda\"" ] }, { @@ -28,11 +28,12 @@ "source": [ "l = 12\n", "g = 6\n", - "para_dict = {'l':l,'g':g}\n", - "code_class = 'bb'\n", + "para_dict = {\"l\": l, \"g\": g}\n", + "code_class = \"bb\"\n", "\n", "from code_construction.code_construction import CodeConstructor\n", - "code_constructor = CodeConstructor(method=code_class,para_dict = para_dict)" + "\n", + "code_constructor = CodeConstructor(method=code_class, para_dict=para_dict)" ] }, { @@ -52,22 +53,24 @@ "source": [ "from bayesian_optimization.objective_function import ObjectiveFunction\n", "\n", - "pp=0.006\n", + "pp = 0.006\n", "\n", "# ------depolarizing noise------\n", "# Obj_Func = ObjectiveFunction(code_constructor, pp=pp)\n", "# ------circuit level noise------\n", - "Obj_Func = ObjectiveFunction(code_constructor,\n", - " pp=pp,\n", - " decoder_param={'trail': 10000,'max_error':100},\n", - " circuit_level_noise = True,\n", - " circuit_param={\n", - " 'noise_model':'SD6',\n", - " 'num_workers' : 24,\n", - " 'rounds':12,\n", - " 'custom_error_model':{},\n", - " 'decoder':'bplsd'\n", - " })\n", + "Obj_Func = ObjectiveFunction(\n", + " code_constructor,\n", + " pp=pp,\n", + " decoder_param={\"trail\": 10000, \"max_error\": 100},\n", + " circuit_level_noise=True,\n", + " circuit_param={\n", + " \"noise_model\": \"SD6\",\n", + " \"num_workers\": 24,\n", + " \"rounds\": 12,\n", + " \"custom_error_model\": {},\n", + " \"decoder\": \"bplsd\",\n", + " },\n", + ")\n", "\n", "\n", "# ------if we use ler function\n", @@ -99,44 +102,54 @@ "import numpy as np\n", "import torch\n", "\n", - "class Get_new_points_function():\n", - " def __init__(self,method='qc-ldpc-hgp',hyperparameters = {'p': 2, 'q': 6, 'm': 2},encode='None'):\n", + "\n", + "class Get_new_points_function:\n", + " def __init__(\n", + " self,\n", + " method=\"qc-ldpc-hgp\",\n", + " hyperparameters={\"p\": 2, \"q\": 6, \"m\": 2},\n", + " encode=\"None\",\n", + " ):\n", " self.method = method\n", " self.hyperparameters = hyperparameters\n", " self.encode = encode\n", " self.init = False\n", "\n", - " def get_new_points_function(self,number):\n", - " if self.method == 'qc-ldpc-hgp':\n", + " def get_new_points_function(self, number):\n", + " if self.method == \"qc-ldpc-hgp\":\n", " new_points = self.get_new_points_HGP(number)\n", - " elif self.method == 'bb':\n", + " elif self.method == \"bb\":\n", " new_points = self.get_new_bb_vector(number)\n", " return new_points\n", - " \n", - " def get_new_points_HGP(self,number):\n", - " return np.random.randint(0, self.hyperparameters['m'] + 1, (number, self.hyperparameters['p'] * self.hyperparameters['q']))\n", "\n", - " def get_new_bb_vector(self,number):\n", + " def get_new_points_HGP(self, number):\n", + " return np.random.randint(\n", + " 0,\n", + " self.hyperparameters[\"m\"] + 1,\n", + " (number, self.hyperparameters[\"p\"] * self.hyperparameters[\"q\"]),\n", + " )\n", + "\n", + " def get_new_bb_vector(self, number):\n", " results = []\n", - " l = self.hyperparameters['l']\n", - " g = self.hyperparameters['g']\n", - " if self.init == False and l==12 and g==999:\n", - " print('best known bb code added to initial points')\n", - " self.init=True\n", - "\n", - " a = np.zeros((l+g-1)*2)\n", - " a[3]=1\n", - " a[11+1]=1\n", - " a[11+2]=1\n", - " a[17+1]=1\n", - " a[17+2]=1\n", - " a[17+11+3]=1\n", + " l = self.hyperparameters[\"l\"]\n", + " g = self.hyperparameters[\"g\"]\n", + " if self.init == False and l == 12 and g == 999:\n", + " print(\"best known bb code added to initial points\")\n", + " self.init = True\n", + "\n", + " a = np.zeros((l + g - 1) * 2)\n", + " a[3] = 1\n", + " a[11 + 1] = 1\n", + " a[11 + 2] = 1\n", + " a[17 + 1] = 1\n", + " a[17 + 2] = 1\n", + " a[17 + 11 + 3] = 1\n", " results.append(a)\n", "\n", - " while number>0:\n", - " new_point = np.random.randint(0,2, size=(l+g-1)*2)\n", + " while number > 0:\n", + " new_point = np.random.randint(0, 2, size=(l + g - 1) * 2)\n", " c = code_constructor.construct(new_point)\n", - " if c.k==0:\n", + " if c.k == 0:\n", " continue\n", " else:\n", " results.append(new_point)\n", @@ -144,7 +157,9 @@ " return np.array(results)\n", "\n", "\n", - "gnp = Get_new_points_function(method=code_class,hyperparameters = para_dict).get_new_points_function" + "gnp = Get_new_points_function(\n", + " method=code_class, hyperparameters=para_dict\n", + ").get_new_points_function" ] }, { @@ -159,15 +174,15 @@ "y_init = []\n", "pl_init = []\n", "for x in X_init:\n", - " y,pl = obj_func(x)\n", + " y, pl = obj_func(x)\n", " y_init.append(y)\n", " pl_init.append(pl)\n", "\n", - "X_init = torch.tensor(X_init,dtype=torch.float32)\n", + "X_init = torch.tensor(X_init, dtype=torch.float32)\n", "X_init.to(DEVICE)\n", - "y_init = torch.tensor(y_init,dtype=torch.float32)\n", + "y_init = torch.tensor(y_init, dtype=torch.float32)\n", "y_init.to(DEVICE)\n", - "pl_init = torch.tensor(pl_init,dtype=torch.float32)\n", + "pl_init = torch.tensor(pl_init, dtype=torch.float32)\n", "pl_init.to(DEVICE)" ] }, @@ -192,39 +207,57 @@ "import gpytorch\n", "from gpytorch.kernels import RBFKernel, MaternKernel, SpectralMixtureKernel, ScaleKernel\n", "\n", - "class E():\n", - " def __init__(self,code_constructor,views_info ):\n", + "\n", + "class E:\n", + " def __init__(self, code_constructor, views_info):\n", " self.code_constructor = code_constructor\n", - " self.encoder = CSSEncoder(views_info,mode ='relations')\n", + " self.encoder = CSSEncoder(views_info, mode=\"relations\")\n", "\n", - " def encode_single(self,x):\n", - " return self.encoder.encode(self.code_constructor.construct(np.array(x).astype(int)))\n", - " def encode(self,x):\n", + " def encode_single(self, x):\n", + " return self.encoder.encode(\n", + " self.code_constructor.construct(np.array(x).astype(int))\n", + " )\n", + "\n", + " def encode(self, x):\n", " # x: B views\n", " return [self.encode_single(i) for i in x]\n", + "\n", + "\n", "views_info = [\n", - " {\"name\":\"decode\",\n", - " \"partite_classes\":[\"SZ\",\"DQ\",\"SX\"],\n", - " \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"], \n", - " \"weight_mode\":\"count\", \"log1p\":True},\n", - " {\"name\":\"xlogic\",\n", - " \"partite_classes\":[\"DQ\",\"SX\",\"LX\"],\n", - " \"relations\":[\"DQ_SX\",\"SX_DQ\",\"LX_DQ\",\"DQ_LX\"],\n", - " \"weight_mode\":\"count\", \"log1p\":True},\n", - " {\"name\":\"zlogic\",\n", - " \"partite_classes\":[\"SZ\",\"DQ\",\"LZ\"],\n", - " \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"LZ_DQ\",\"DQ_LZ\"],\n", - " \"weight_mode\":\"count\", \"log1p\":True},\n", + " {\n", + " \"name\": \"decode\",\n", + " \"partite_classes\": [\"SZ\", \"DQ\", \"SX\"],\n", + " \"relations\": [\"SZ_DQ\", \"DQ_SZ\", \"DQ_SX\", \"SX_DQ\"],\n", + " \"weight_mode\": \"count\",\n", + " \"log1p\": True,\n", + " },\n", + " {\n", + " \"name\": \"xlogic\",\n", + " \"partite_classes\": [\"DQ\", \"SX\", \"LX\"],\n", + " \"relations\": [\"DQ_SX\", \"SX_DQ\", \"LX_DQ\", \"DQ_LX\"],\n", + " \"weight_mode\": \"count\",\n", + " \"log1p\": True,\n", + " },\n", + " {\n", + " \"name\": \"zlogic\",\n", + " \"partite_classes\": [\"SZ\", \"DQ\", \"LZ\"],\n", + " \"relations\": [\"SZ_DQ\", \"DQ_SZ\", \"LZ_DQ\", \"DQ_LZ\"],\n", + " \"weight_mode\": \"count\",\n", + " \"log1p\": True,\n", + " },\n", "]\n", "\n", - "def get_model_(X, y, kernel_type='ard_rbf', mean_type='linear', mean_input=64,embed_dim=128):\n", "\n", - " encoder = E(code_constructor,views_info)\n", + "def get_model_(\n", + " X, y, kernel_type=\"ard_rbf\", mean_type=\"linear\", mean_input=64, embed_dim=128\n", + "):\n", + "\n", + " encoder = E(code_constructor, views_info)\n", "\n", " # NN embedder\n", " embedding = ChainComplexEmbedder(\n", " views_info=views_info,\n", - " d_model=embed_dim ,\n", + " d_model=embed_dim,\n", " num_layers=4,\n", " view_aggr=\"sum\",\n", " num_bases=4,\n", @@ -234,19 +267,18 @@ " dropout=0.1,\n", " ).to(device)\n", "\n", - "\n", - "\n", " # kernel\n", - " if kernel_type == 'ard_rbf':\n", + " if kernel_type == \"ard_rbf\":\n", " base = RBFKernel(ard_num_dims=embed_dim)\n", " kernel = ScaleKernel(base)\n", - " elif kernel_type == 'matern':\n", + " elif kernel_type == \"matern\":\n", " base = MaternKernel(nu=1.5, ard_num_dims=embed_dim)\n", " kernel = ScaleKernel(base)\n", - " elif kernel_type == 'spectral_mixture':\n", + " elif kernel_type == \"spectral_mixture\":\n", " kernel = SpectralMixtureKernel(num_mixtures=4, ard_num_dims=embed_dim)\n", - " elif kernel_type == 'rbf_plus_periodic':\n", + " elif kernel_type == \"rbf_plus_periodic\":\n", " from gpytorch.kernels import PeriodicKernel\n", + "\n", " rbf = ScaleKernel(RBFKernel(ard_num_dims=embed_dim))\n", " periodic = ScaleKernel(PeriodicKernel(ard_num_dims=embed_dim))\n", " kernel = rbf + periodic\n", @@ -261,7 +293,8 @@ " train_y = y.to(device).float().view(-1)\n", "\n", " gp = GaussianProcess_QEC(\n", - " train_x, train_y,\n", + " train_x,\n", + " train_y,\n", " likelihood=likelihood,\n", " kernel=kernel,\n", " encoder=encoder.encode,\n", @@ -271,11 +304,13 @@ " ).to(device)\n", "\n", " return gp\n", + "\n", + "\n", "model = get_model_(\n", " X_init,\n", " pl_init,\n", - " kernel_type='matern',\n", - " mean_type='linear',\n", + " kernel_type=\"matern\",\n", + " mean_type=\"linear\",\n", " mean_input=128,\n", ")" ] @@ -312,28 +347,26 @@ " self,\n", " model: gpytorch.models.ExactGP,\n", " device: str = \"cpu\",\n", - "\n", " training_iter: int = 80,\n", - " lr: Optional[Dict[str, float]] = None, # {'embed':4e-4,'mean':1e-3,'kernel':1e-3,'like':2e-2}\n", + " lr: Optional[\n", + " Dict[str, float]\n", + " ] = None, # {'embed':4e-4,'mean':1e-3,'kernel':1e-3,'like':2e-2}\n", " weight_decay: float = 1e-4,\n", " max_grad_norm: float = 2.0,\n", " optimizer_type: str = \"adamw\",\n", " recreate_optimizer_each_round: bool = True,\n", - "\n", - " scheduler_cfg: Optional[Dict[str, Any]] = None, # {'factor':0.5,'patience':5,'min_lr':1e-6}\n", - " early_stopping: Optional[Dict[str, Any]] = None,# {'patience':10,'tol':1e-4}\n", - "\n", + " scheduler_cfg: Optional[\n", + " Dict[str, Any]\n", + " ] = None, # {'factor':0.5,'patience':5,'min_lr':1e-6}\n", + " early_stopping: Optional[Dict[str, Any]] = None, # {'patience':10,'tol':1e-4}\n", " use_priors: bool = True,\n", " priors_cfg: Optional[Dict[str, Dict[str, float]]] = None,\n", - " noise_floor: float = 1e-3, # lower bound (in z-domain) for likelihood noise\n", + " noise_floor: float = 1e-3, # lower bound (in z-domain) for likelihood noise\n", " lengthscale_bounds: Optional[Tuple[float, float]] = None,\n", - "\n", " warm_start: bool = True,\n", - " rescale_on_scaler_change: bool = True, # alpha = old_std / new_std\n", + " rescale_on_scaler_change: bool = True, # alpha = old_std / new_std\n", " carry_optimizer_state: bool = False,\n", - "\n", - " freeze_cfg: Optional[Dict[str, Any]] = None, # {'lengthscale_until_round': 2}\n", - "\n", + " freeze_cfg: Optional[Dict[str, Any]] = None, # {'lengthscale_until_round': 2}\n", " verbose: bool = True,\n", " log_every: Optional[int] = None,\n", " save_best_state: bool = True,\n", @@ -347,14 +380,18 @@ " self.optimizer_type = optimizer_type.lower()\n", " self.recreate_optimizer_each_round = bool(recreate_optimizer_each_round)\n", "\n", - " self.scheduler_cfg = scheduler_cfg or {'factor': 0.5, 'patience': 5, 'min_lr': 1e-6}\n", - " self.early_stopping = early_stopping or {'patience': 10, 'tol': 1e-4}\n", + " self.scheduler_cfg = scheduler_cfg or {\n", + " \"factor\": 0.5,\n", + " \"patience\": 5,\n", + " \"min_lr\": 1e-6,\n", + " }\n", + " self.early_stopping = early_stopping or {\"patience\": 10, \"tol\": 1e-4}\n", "\n", " self.use_priors = bool(use_priors)\n", " self.priors_cfg = priors_cfg or {\n", - " 'lengthscale': {'type': 'lognormal', 'loc': 0.0, 'scale': 0.5},\n", - " 'outputscale': {'type': 'lognormal', 'loc': 0.0, 'scale': 0.5},\n", - " 'noise': {'type': 'lognormal', 'loc': -4.0, 'scale': 0.5},\n", + " \"lengthscale\": {\"type\": \"lognormal\", \"loc\": 0.0, \"scale\": 0.5},\n", + " \"outputscale\": {\"type\": \"lognormal\", \"loc\": 0.0, \"scale\": 0.5},\n", + " \"noise\": {\"type\": \"lognormal\", \"loc\": -4.0, \"scale\": 0.5},\n", " }\n", " self.noise_floor = float(noise_floor)\n", " self.lengthscale_bounds = lengthscale_bounds\n", @@ -363,12 +400,12 @@ " self.rescale_on_scaler_change = bool(rescale_on_scaler_change)\n", " self.carry_optimizer_state = bool(carry_optimizer_state)\n", "\n", - " self.freeze_cfg = freeze_cfg or {'lengthscale_until_round': 0}\n", + " self.freeze_cfg = freeze_cfg or {\"lengthscale_until_round\": 0}\n", "\n", " self.verbose = bool(verbose)\n", " self.log_every = log_every\n", " self.save_best_state = bool(save_best_state)\n", - " self.lr = lr or {'embed': 4e-4, 'mean': 1e-3, 'kernel': 1e-3, 'like': 2e-2}\n", + " self.lr = lr or {\"embed\": 4e-4, \"mean\": 1e-3, \"kernel\": 1e-3, \"like\": 2e-2}\n", "\n", " # ---- State ----\n", " self._optimizer: Optional[torch.optim.Optimizer] = None\n", @@ -406,15 +443,17 @@ " return\n", " alpha = float(old_std / new_std)\n", " with torch.no_grad():\n", - " if hasattr(self.model, \"covar_module\") and hasattr(self.model.covar_module, \"outputscale\"):\n", - " self.model.covar_module.outputscale.mul_(alpha ** 2)\n", + " if hasattr(self.model, \"covar_module\") and hasattr(\n", + " self.model.covar_module, \"outputscale\"\n", + " ):\n", + " self.model.covar_module.outputscale.mul_(alpha**2)\n", " try:\n", - " self.model.likelihood.noise.mul_(alpha ** 2)\n", + " self.model.likelihood.noise.mul_(alpha**2)\n", " except Exception:\n", " # Fallback: attempt to read/set noise through likelihood noise_covar\n", " try:\n", " noise = self._get_noise_value()\n", - " self._set_noise_value(noise * (alpha ** 2))\n", + " self._set_noise_value(noise * (alpha**2))\n", " except Exception:\n", " pass\n", " self._last_scaler_std = new_std\n", @@ -424,9 +463,11 @@ " Optionally freeze base kernel lengthscale parameters for early rounds.\n", " \"\"\"\n", " self._round_idx = int(round_idx)\n", - " until = int(self.freeze_cfg.get('lengthscale_until_round', 0))\n", - " freeze = (round_idx <= until)\n", - " base_kernel = getattr(getattr(self.model, \"covar_module\", None), \"base_kernel\", None)\n", + " until = int(self.freeze_cfg.get(\"lengthscale_until_round\", 0))\n", + " freeze = round_idx <= until\n", + " base_kernel = getattr(\n", + " getattr(self.model, \"covar_module\", None), \"base_kernel\", None\n", + " )\n", " if base_kernel is not None:\n", " for p in base_kernel.parameters():\n", " p.requires_grad_(not freeze)\n", @@ -441,9 +482,14 @@ " - best-state checkpointing (optional).\n", " \"\"\"\n", " model = self.model\n", - " model.train(); model.likelihood.train()\n", - "\n", - " if self._optimizer is None or self.recreate_optimizer_each_round or not self.carry_optimizer_state:\n", + " model.train()\n", + " model.likelihood.train()\n", + "\n", + " if (\n", + " self._optimizer is None\n", + " or self.recreate_optimizer_each_round\n", + " or not self.carry_optimizer_state\n", + " ):\n", " self._optimizer = self._build_optimizer()\n", " self._scheduler = self._build_scheduler(self._optimizer)\n", " elif self._scheduler is None:\n", @@ -454,8 +500,8 @@ " best_loss = float(\"inf\")\n", " best_state = None\n", " no_improve = 0\n", - " patience = int(self.early_stopping.get('patience', 10))\n", - " tol = float(self.early_stopping.get('tol', 1e-4))\n", + " patience = int(self.early_stopping.get(\"patience\", 10))\n", + " tol = float(self.early_stopping.get(\"tol\", 1e-4))\n", " self._history = []\n", "\n", " train_x, train_y = model.train_inputs[0], model.train_targets\n", @@ -483,21 +529,29 @@ " if self.save_best_state:\n", " best_state = {\n", " \"model\": copy.deepcopy(model.state_dict()),\n", - " \"likelihood\": copy.deepcopy(model.likelihood.state_dict())\n", + " \"likelihood\": copy.deepcopy(model.likelihood.state_dict()),\n", " }\n", " else:\n", " no_improve += 1\n", "\n", - " if self.verbose and (self.log_every is None or it % max(1, self.log_every) == 0 or it == self.training_iter):\n", + " if self.verbose and (\n", + " self.log_every is None\n", + " or it % max(1, self.log_every) == 0\n", + " or it == self.training_iter\n", + " ):\n", " ls_val = self._safe_get_lengthscale()\n", - " out_v = self._safe_get_outputscale()\n", - " nz_v = self._safe_get_noise()\n", - " print(f\"[round {self._round_idx:>3d} | {it:>4d}/{self.training_iter}] \"\n", - " f\"nll={cur:.4f} len={ls_val} out={out_v:.3e} noise={nz_v:.3e}\")\n", + " out_v = self._safe_get_outputscale()\n", + " nz_v = self._safe_get_noise()\n", + " print(\n", + " f\"[round {self._round_idx:>3d} | {it:>4d}/{self.training_iter}] \"\n", + " f\"nll={cur:.4f} len={ls_val} out={out_v:.3e} noise={nz_v:.3e}\"\n", + " )\n", "\n", " if no_improve >= patience and rel_impr < tol:\n", " if self.verbose:\n", - " print(f\"[round {self._round_idx:>3d}] early stop @ {it}, best nll={best_loss:.4f}\")\n", + " print(\n", + " f\"[round {self._round_idx:>3d}] early stop @ {it}, best nll={best_loss:.4f}\"\n", + " )\n", " break\n", "\n", " if self.save_best_state and best_state is not None:\n", @@ -519,17 +573,39 @@ " # ================= Internal Utilities =================\n", " def _build_optimizer(self) -> torch.optim.Optimizer:\n", " # Group parameters by module for separate learning rates.\n", - " params_embed = list(getattr(self.model, \"embed\", torch.nn.Module()).parameters())\n", - " params_kernel = list(getattr(self.model, \"covar_module\", torch.nn.Module()).parameters())\n", - " params_like = list(self.model.likelihood.parameters())\n", - " params_mean = list(getattr(self.model, \"mean_module\", torch.nn.Module()).parameters())\n", + " params_embed = list(\n", + " getattr(self.model, \"embed\", torch.nn.Module()).parameters()\n", + " )\n", + " params_kernel = list(\n", + " getattr(self.model, \"covar_module\", torch.nn.Module()).parameters()\n", + " )\n", + " params_like = list(self.model.likelihood.parameters())\n", + " params_mean = list(\n", + " getattr(self.model, \"mean_module\", torch.nn.Module()).parameters()\n", + " )\n", "\n", " lr = self.lr\n", " groups = [\n", - " {'params': params_embed, 'lr': lr.get('embed', 4e-4), 'weight_decay': self.weight_decay},\n", - " {'params': params_mean, 'lr': lr.get('mean', 1e-3), 'weight_decay': self.weight_decay},\n", - " {'params': params_kernel, 'lr': lr.get('kernel', 1e-3), 'weight_decay': self.weight_decay},\n", - " {'params': params_like, 'lr': lr.get('like', 2e-2), 'weight_decay': self.weight_decay/5},\n", + " {\n", + " \"params\": params_embed,\n", + " \"lr\": lr.get(\"embed\", 4e-4),\n", + " \"weight_decay\": self.weight_decay,\n", + " },\n", + " {\n", + " \"params\": params_mean,\n", + " \"lr\": lr.get(\"mean\", 1e-3),\n", + " \"weight_decay\": self.weight_decay,\n", + " },\n", + " {\n", + " \"params\": params_kernel,\n", + " \"lr\": lr.get(\"kernel\", 1e-3),\n", + " \"weight_decay\": self.weight_decay,\n", + " },\n", + " {\n", + " \"params\": params_like,\n", + " \"lr\": lr.get(\"like\", 2e-2),\n", + " \"weight_decay\": self.weight_decay / 5,\n", + " },\n", " ]\n", " if self.optimizer_type == \"adam\":\n", " return torch.optim.Adam(groups)\n", @@ -538,36 +614,44 @@ " def _build_scheduler(self, optimizer) -> torch.optim.lr_scheduler.ReduceLROnPlateau:\n", " cfg = self.scheduler_cfg\n", " return torch.optim.lr_scheduler.ReduceLROnPlateau(\n", - " optimizer, mode='min',\n", - " factor=float(cfg.get('factor', 0.5)),\n", - " patience=int(cfg.get('patience', 5)),\n", - " min_lr=float(cfg.get('min_lr', 1e-6)),\n", - " verbose=False\n", + " optimizer,\n", + " mode=\"min\",\n", + " factor=float(cfg.get(\"factor\", 0.5)),\n", + " patience=int(cfg.get(\"patience\", 5)),\n", + " min_lr=float(cfg.get(\"min_lr\", 1e-6)),\n", + " verbose=False,\n", " )\n", "\n", " def _register_priors_safely(self):\n", " \"\"\"Attach priors to the **constrained** parameter names (lengthscale/outputscale/noise).\"\"\"\n", " from gpytorch.priors import LogNormalPrior\n", + "\n", " # lengthscale prior\n", " try:\n", " bk = self.model.covar_module.base_kernel\n", - " loc = float(self.priors_cfg['lengthscale']['loc'])\n", - " scale = float(self.priors_cfg['lengthscale']['scale'])\n", - " bk.register_prior(\"lengthscale_prior\", LogNormalPrior(loc, scale), \"lengthscale\")\n", + " loc = float(self.priors_cfg[\"lengthscale\"][\"loc\"])\n", + " scale = float(self.priors_cfg[\"lengthscale\"][\"scale\"])\n", + " bk.register_prior(\n", + " \"lengthscale_prior\", LogNormalPrior(loc, scale), \"lengthscale\"\n", + " )\n", " except Exception:\n", " pass\n", " # outputscale prior\n", " try:\n", - " loc = float(self.priors_cfg['outputscale']['loc'])\n", - " scale = float(self.priors_cfg['outputscale']['scale'])\n", - " self.model.covar_module.register_prior(\"outputscale_prior\", LogNormalPrior(loc, scale), \"outputscale\")\n", + " loc = float(self.priors_cfg[\"outputscale\"][\"loc\"])\n", + " scale = float(self.priors_cfg[\"outputscale\"][\"scale\"])\n", + " self.model.covar_module.register_prior(\n", + " \"outputscale_prior\", LogNormalPrior(loc, scale), \"outputscale\"\n", + " )\n", " except Exception:\n", " pass\n", " # noise prior\n", " try:\n", - " loc = float(self.priors_cfg['noise']['loc'])\n", - " scale = float(self.priors_cfg['noise']['scale'])\n", - " self.model.likelihood.register_prior(\"noise_prior\", LogNormalPrior(loc, scale), \"noise\")\n", + " loc = float(self.priors_cfg[\"noise\"][\"loc\"])\n", + " scale = float(self.priors_cfg[\"noise\"][\"scale\"])\n", + " self.model.likelihood.register_prior(\n", + " \"noise_prior\", LogNormalPrior(loc, scale), \"noise\"\n", + " )\n", " except Exception:\n", " pass\n", "\n", @@ -575,7 +659,10 @@ " # Noise lower bound (constraint on raw_noise)\n", " try:\n", " self.model.likelihood.noise_covar.register_constraint(\n", - " \"raw_noise\", gpytorch.constraints.GreaterThan(torch.tensor(self.noise_floor, device=self.device))\n", + " \"raw_noise\",\n", + " gpytorch.constraints.GreaterThan(\n", + " torch.tensor(self.noise_floor, device=self.device)\n", + " ),\n", " )\n", " except Exception:\n", " pass\n", @@ -608,7 +695,9 @@ " return float(self.model.likelihood.noise.detach().cpu().item())\n", " except Exception:\n", " try:\n", - " return float(self.model.likelihood.noise_covar.noise.detach().cpu().item())\n", + " return float(\n", + " self.model.likelihood.noise_covar.noise.detach().cpu().item()\n", + " )\n", " except Exception:\n", " return float(\"nan\")\n", "\n", @@ -618,12 +707,14 @@ " self.model.likelihood.noise.copy_(torch.tensor(v, device=self.device))\n", " except Exception:\n", " try:\n", - " self.model.likelihood.noise_covar.noise.copy_(torch.tensor(v, device=self.device))\n", + " self.model.likelihood.noise_covar.noise.copy_(\n", + " torch.tensor(v, device=self.device)\n", + " )\n", " except Exception:\n", " pass\n", "\n", " def _safe_get_noise(self) -> float:\n", - " return self._get_noise_value()\n" + " return self._get_noise_value()" ] }, { @@ -636,36 +727,30 @@ "trainer = GPTrainer(\n", " model=model,\n", " device=DEVICE,\n", - "\n", " # --- Training and regularization ---\n", " training_iter=80,\n", - " lr={'embed': 4e-4, 'mean': 1e-3, 'kernel': 1e-3, 'like': 2e-2},\n", + " lr={\"embed\": 4e-4, \"mean\": 1e-3, \"kernel\": 1e-3, \"like\": 2e-2},\n", " weight_decay=1e-4,\n", " max_grad_norm=2.0,\n", - " optimizer_type='adamw',\n", + " optimizer_type=\"adamw\",\n", " recreate_optimizer_each_round=True,\n", - "\n", " # --- Scheduler and early stopping ---\n", - " scheduler_cfg={'factor': 0.5, 'patience': 5, 'min_lr': 1e-6},\n", - " early_stopping={'patience': 10, 'tol': 1e-4},\n", - "\n", + " scheduler_cfg={\"factor\": 0.5, \"patience\": 5, \"min_lr\": 1e-6},\n", + " early_stopping={\"patience\": 10, \"tol\": 1e-4},\n", " # --- Priors and constraints (helpful for small datasets) ---\n", " use_priors=True,\n", - " noise_floor=1e-3, # Lower bound for z-domain noise to avoid overfitting\n", - " lengthscale_bounds=None, # If X is normalized to [0,1], one may use (1e-2, 10.)\n", - "\n", + " noise_floor=1e-3, # Lower bound for z-domain noise to avoid overfitting\n", + " lengthscale_bounds=None, # If X is normalized to [0,1], one may use (1e-2, 10.)\n", " # --- Warm start and scaler rescaling ---\n", " warm_start=True,\n", - " rescale_on_scaler_change=True, # Recommended: True\n", + " rescale_on_scaler_change=True, # Recommended: True\n", " carry_optimizer_state=False,\n", - "\n", " # --- Freeze base kernel lengthscale in early rounds (stabilizes small-data regime) ---\n", - " freeze_cfg={'lengthscale_until_round': 2},\n", - "\n", + " freeze_cfg={\"lengthscale_until_round\": 2},\n", " verbose=True,\n", " log_every=8,\n", " save_best_state=True,\n", - ")\n" + ")" ] }, { @@ -695,9 +780,10 @@ "@dataclass\n", "class LogStdStats:\n", " \"\"\"Stores the fitted statistics (mean, std, eps) for log-domain normalization.\"\"\"\n", - " mean: float # mean of log(pl + eps)\n", + "\n", + " mean: float # mean of log(pl + eps)\n", " scale: float # std of log(pl + eps)\n", - " eps: float # epsilon used in log(pl + eps)\n", + " eps: float # epsilon used in log(pl + eps)\n", "\n", "\n", "class LogStdNormalizer:\n", @@ -710,7 +796,13 @@ " (μ_z, σ_z) in z-space back to the probability domain, using log-normal moments.\n", " \"\"\"\n", "\n", - " def __init__(self, eps: float = 1e-8, min_prob: float = 1e-12, max_prob: float = 1.0 - 1e-12, device: str = \"cpu\"):\n", + " def __init__(\n", + " self,\n", + " eps: float = 1e-8,\n", + " min_prob: float = 1e-12,\n", + " max_prob: float = 1.0 - 1e-12,\n", + " device: str = \"cpu\",\n", + " ):\n", " self.stats: LogStdStats = LogStdStats(mean=0.0, scale=1.0, eps=float(eps))\n", " self._fitted: bool = False\n", " self.min_prob = float(min_prob)\n", @@ -739,7 +831,7 @@ " pl_t = self._clamp_prob(pl_t)\n", " ylog = torch.log(pl_t + self.stats.eps)\n", " mean = ylog.mean().item()\n", - " std = ylog.std(unbiased=False).item() # population standard deviation\n", + " std = ylog.std(unbiased=False).item() # population standard deviation\n", "\n", " # Avoid degenerate scaling (very small std can cause instability)\n", " if std < 1e-12:\n", @@ -777,7 +869,9 @@ "\n", " # -------------------- Posterior inverse transform (used in EI acquisition) --------------------\n", " @torch.no_grad()\n", - " def inverse_mean_std(self, mu_z: TensorLike, std_z: TensorLike) -> Tuple[torch.Tensor, torch.Tensor]:\n", + " def inverse_mean_std(\n", + " self, mu_z: TensorLike, std_z: TensorLike\n", + " ) -> Tuple[torch.Tensor, torch.Tensor]:\n", " \"\"\"\n", " If z ~ N(mu_z, std_z^2), then log(pl + eps) ~ N(mu, std^2), where:\n", " mu = mu_z * scale + mean\n", @@ -791,19 +885,19 @@ " (mean_pl, std_pl): expected mean and std of pl in probability domain.\n", " \"\"\"\n", " assert self._fitted, \"Call fit(pl_train) before inverse_mean_std.\"\n", - " mu_z_t = self._to_tensor(mu_z, dtype=torch.float32)\n", + " mu_z_t = self._to_tensor(mu_z, dtype=torch.float32)\n", " std_z_t = self._to_tensor(std_z, dtype=torch.float32).abs()\n", "\n", - " mu = mu_z_t * self.stats.scale + self.stats.mean\n", + " mu = mu_z_t * self.stats.scale + self.stats.mean\n", " std = std_z_t * abs(self.stats.scale)\n", "\n", " # E[pl + eps] and Var(pl + eps)\n", " exp_half_var = torch.exp(0.5 * std**2)\n", " mean_pl_plus = torch.exp(mu) * exp_half_var\n", - " var_pl_plus = (torch.exp(std**2) - 1.0) * torch.exp(2.0 * mu + std**2)\n", + " var_pl_plus = (torch.exp(std**2) - 1.0) * torch.exp(2.0 * mu + std**2)\n", "\n", " mean_pl = mean_pl_plus - self.stats.eps\n", - " std_pl = var_pl_plus.clamp_min(1e-30).sqrt()\n", + " std_pl = var_pl_plus.clamp_min(1e-30).sqrt()\n", "\n", " # Clamp mean within valid probability range\n", " mean_pl = self._clamp_prob(mean_pl)\n", @@ -829,17 +923,19 @@ " \"fitted\": self._fitted,\n", " \"min_prob\": self.min_prob,\n", " \"max_prob\": self.max_prob,\n", - " \"device\": self.device\n", + " \"device\": self.device,\n", " }\n", "\n", " def load_state_dict(self, state: Dict[str, Any]):\n", " \"\"\"Restore normalizer state from a dict.\"\"\"\n", " s = state[\"stats\"]\n", - " self.stats = LogStdStats(mean=float(s[\"mean\"]), scale=float(s[\"scale\"]), eps=float(s[\"eps\"]))\n", + " self.stats = LogStdStats(\n", + " mean=float(s[\"mean\"]), scale=float(s[\"scale\"]), eps=float(s[\"eps\"])\n", + " )\n", " self._fitted = bool(state.get(\"fitted\", True))\n", " self.min_prob = float(state.get(\"min_prob\", self.min_prob))\n", " self.max_prob = float(state.get(\"max_prob\", self.max_prob))\n", - " self.device = state.get(\"device\", self.device)\n" + " self.device = state.get(\"device\", self.device)" ] }, { @@ -885,21 +981,37 @@ " Epsilon used in log(pl + eps) transform.\n", " \"\"\"\n", "\n", - " def __init__(self, pl_to_obj_fn, best_value=None, jitter=1e-2, eps=1e-9,\n", - " device=\"cpu\", normalizer=None, prob_lower=1e-12, prob_upper=1.0 - 1e-12,\n", - " log_eps=1e-8):\n", + " def __init__(\n", + " self,\n", + " pl_to_obj_fn,\n", + " best_value=None,\n", + " jitter=1e-2,\n", + " eps=1e-9,\n", + " device=\"cpu\",\n", + " normalizer=None,\n", + " prob_lower=1e-12,\n", + " prob_upper=1.0 - 1e-12,\n", + " log_eps=1e-8,\n", + " ):\n", " self.pl_to_obj_fn = pl_to_obj_fn\n", - " self.best_value = best_value\n", - " self.jitter = float(jitter)\n", - " self.eps = float(eps)\n", - " self.device = device\n", - " self.prob_lower = float(prob_lower)\n", - " self.prob_upper = float(prob_upper)\n", - " self.log_eps = float(log_eps)\n", + " self.best_value = best_value\n", + " self.jitter = float(jitter)\n", + " self.eps = float(eps)\n", + " self.device = device\n", + " self.prob_lower = float(prob_lower)\n", + " self.prob_upper = float(prob_upper)\n", + " self.log_eps = float(log_eps)\n", "\n", " # EI maintains its own normalizer; create one if not provided.\n", - " self.normalizer = normalizer if normalizer is not None else LogStdNormalizer(\n", - " eps=self.log_eps, device=self.device, min_prob=self.prob_lower, max_prob=self.prob_upper\n", + " self.normalizer = (\n", + " normalizer\n", + " if normalizer is not None\n", + " else LogStdNormalizer(\n", + " eps=self.log_eps,\n", + " device=self.device,\n", + " min_prob=self.prob_lower,\n", + " max_prob=self.prob_upper,\n", + " )\n", " )\n", "\n", " # ---------- Interface exposed to BO loop ----------\n", @@ -928,9 +1040,11 @@ " return ret[0], ret[1]\n", " if isinstance(ret, dict):\n", " mu = ret.get(\"mean\", ret.get(\"mu\"))\n", - " sd = ret.get(\"std\", ret.get(\"sigma\"))\n", + " sd = ret.get(\"std\", ret.get(\"sigma\"))\n", " if mu is None or sd is None:\n", - " raise RuntimeError(\"pl_to_obj_fn dict must contain mean/std (or mu/sigma).\")\n", + " raise RuntimeError(\n", + " \"pl_to_obj_fn dict must contain mean/std (or mu/sigma).\"\n", + " )\n", " return mu, sd\n", " raise RuntimeError(f\"Unsupported return type from pl_to_obj_fn: {type(ret)}\")\n", "\n", @@ -947,7 +1061,9 @@ " EI(x) = (μ_f - f* - ξ) Φ(Z) + σ_f φ(Z),\n", " where Z = (μ_f - f* - ξ) / σ_f, Φ = CDF, φ = PDF of standard normal.\n", " \"\"\"\n", - " assert self.best_value is not None, \"best_value not set; call set_best_value() first.\"\n", + " assert self.best_value is not None, (\n", + " \"best_value not set; call set_best_value() first.\"\n", + " )\n", "\n", " # Ensure X is a proper tensor on the correct device\n", " if not torch.is_tensor(X):\n", @@ -963,11 +1079,11 @@ "\n", " # Posterior in latent z-domain\n", " with gpytorch.settings.fast_pred_var():\n", - " if hasattr(gp, \"posterior\"): # BoTorch-style API\n", + " if hasattr(gp, \"posterior\"): # BoTorch-style API\n", " post = gp.posterior(X)\n", " mu_lat = post.mean.reshape(-1)\n", " var_lat = post.variance.reshape(-1)\n", - " else: # Pure GPyTorch model\n", + " else: # Pure GPyTorch model\n", " mvn = gp(X)\n", " mu_lat = mvn.mean.reshape(-1)\n", " var_lat = mvn.variance.reshape(-1)\n", @@ -976,7 +1092,9 @@ "\n", " # Convert (μ_z, σ_z) → probability domain\n", " if not self.normalizer.is_fitted():\n", - " raise RuntimeError(\"EI normalizer is not fitted. Call update_normalizer(pl_train) first.\")\n", + " raise RuntimeError(\n", + " \"EI normalizer is not fitted. Call update_normalizer(pl_train) first.\"\n", + " )\n", " mu_pl, std_pl = self.normalizer.inverse_mean_std(mu_lat, std_lat)\n", "\n", " # Clamp probability and std for stability\n", @@ -989,8 +1107,16 @@ " for i in range(mu_pl.numel()):\n", " out = self.pl_to_obj_fn(X[i], mu_pl[i], std_pl[i])\n", " mu_i, sd_i = self._take_mean_std(out)\n", - " mu_obj[i] = mu_i if torch.is_tensor(mu_i) else torch.tensor(mu_i, device=self.device, dtype=torch.float32)\n", - " std_obj[i] = sd_i if torch.is_tensor(sd_i) else torch.tensor(sd_i, device=self.device, dtype=torch.float32)\n", + " mu_obj[i] = (\n", + " mu_i\n", + " if torch.is_tensor(mu_i)\n", + " else torch.tensor(mu_i, device=self.device, dtype=torch.float32)\n", + " )\n", + " std_obj[i] = (\n", + " sd_i\n", + " if torch.is_tensor(sd_i)\n", + " else torch.tensor(sd_i, device=self.device, dtype=torch.float32)\n", + " )\n", "\n", " std_obj = std_obj.clamp_min(self.eps)\n", "\n", @@ -999,7 +1125,7 @@ " Z = imp / std_obj\n", " normal = Normal(torch.zeros_like(mu_obj), torch.ones_like(std_obj))\n", " ei = imp * normal.cdf(Z) + std_obj * torch.exp(normal.log_prob(Z))\n", - " return ei\n" + " return ei" ] }, { @@ -1010,15 +1136,15 @@ "outputs": [], "source": [ "acq = EIAcquisitionFunction(\n", - " pl_to_obj_fn = pl_to_obj,\n", - " best_value = None, \n", - " jitter = 1e-2,\n", - " eps = 1e-9,\n", - " device = DEVICE,\n", - " normalizer = None, \n", - " prob_lower = 1e-12,\n", - " prob_upper = 1.0 - 1e-12,\n", - " log_eps = 1e-8\n", + " pl_to_obj_fn=pl_to_obj,\n", + " best_value=None,\n", + " jitter=1e-2,\n", + " eps=1e-9,\n", + " device=DEVICE,\n", + " normalizer=None,\n", + " prob_lower=1e-12,\n", + " prob_upper=1.0 - 1e-12,\n", + " log_eps=1e-8,\n", ")" ] }, @@ -1040,14 +1166,19 @@ "import numpy as np\n", "import torch\n", "\n", + "\n", "class HillClimbing:\n", - " def __init__(self, next_points_num, gnp, acquisition, device=\"cuda\", validator=None):\n", + " def __init__(\n", + " self, next_points_num, gnp, acquisition, device=\"cuda\", validator=None\n", + " ):\n", "\n", " self.next_points_num = int(next_points_num)\n", " self.gnp = gnp\n", " self.acquisition = acquisition\n", " self.device = device\n", - " self.validator = validator # 例如 lambda z: code_constructor.construct(z).k != 0\n", + " self.validator = (\n", + " validator # 例如 lambda z: code_constructor.construct(z).k != 0\n", + " )\n", "\n", " def hill_climbing_neighbors(self, x: torch.Tensor) -> torch.Tensor:\n", "\n", @@ -1065,7 +1196,7 @@ " def __call__(self, gp) -> torch.Tensor:\n", "\n", " # 起点:np -> torch.float32 on device\n", - " X0_np = self.gnp(self.next_points_num) # [n, d], 0/1\n", + " X0_np = self.gnp(self.next_points_num) # [n, d], 0/1\n", " X0 = torch.tensor(X0_np, dtype=torch.float32, device=self.device)\n", "\n", " best_list = []\n", @@ -1090,21 +1221,21 @@ "\n", " cand = torch.stack(best_list, dim=0) # [n, d], float32, 0/1\n", "\n", - "\n", " if self.validator is not None:\n", " cand_np = cand.detach().cpu().numpy().astype(np.int64)\n", " for i in range(cand_np.shape[0]):\n", " if not self.validator(cand_np[i]):\n", - "\n", " tries = 0\n", " while not self.validator(cand_np[i]):\n", " tries += 1\n", - " cand_np[i] = np.random.randint(0, 2, cand_np[i].shape, dtype=np.int64)\n", + " cand_np[i] = np.random.randint(\n", + " 0, 2, cand_np[i].shape, dtype=np.int64\n", + " )\n", " if tries > 1000:\n", - " break \n", + " break\n", " cand = torch.tensor(cand_np, dtype=torch.float32, device=self.device)\n", "\n", - " return cand # [n, d], float32(0/1), on device\n" + " return cand # [n, d], float32(0/1), on device" ] }, { @@ -1116,14 +1247,16 @@ "source": [ "def bb_validator(candidate_np):\n", " return code_constructor.construct(candidate_np).k != 0\n", + "\n", + "\n", "next_points_num = 4\n", "\n", "hc = HillClimbing(\n", - " next_points_num = next_points_num, # the candidate number\n", - " gnp = gnp, # get new points function\n", - " acquisition = acq, \n", - " device = DEVICE,\n", - " validator = bb_validator\n", + " next_points_num=next_points_num, # the candidate number\n", + " gnp=gnp, # get new points function\n", + " acquisition=acq,\n", + " device=DEVICE,\n", + " validator=bb_validator,\n", ")" ] }, @@ -1146,20 +1279,20 @@ "\n", "bo_iterations = 50\n", "bo = BO_on_QEC(\n", - " gp = model,\n", - " gp_trainer = trainer, \n", - " acquisition_function = acq,\n", - " suggest_next = hc,\n", - " objective_function = obj_func,\n", - " initial_X = X_init,\n", - " initial_pl = pl_init,\n", - " initial_y = y_init,\n", - " BO_iterations = bo_iterations,\n", - " description = 'BB-BO (GP+EI+HC)',\n", - " device = DEVICE,\n", - " pretrain = True \n", + " gp=model,\n", + " gp_trainer=trainer,\n", + " acquisition_function=acq,\n", + " suggest_next=hc,\n", + " objective_function=obj_func,\n", + " initial_X=X_init,\n", + " initial_pl=pl_init,\n", + " initial_y=y_init,\n", + " BO_iterations=bo_iterations,\n", + " description=\"BB-BO (GP+EI+HC)\",\n", + " device=DEVICE,\n", + " pretrain=True,\n", ")\n", - "best_x,best_y,evaluation_history = bo.run()" + "best_x, best_y, evaluation_history = bo.run()" ] }, { @@ -1194,23 +1327,21 @@ "source": [ "# Flatten the provided evaluation history into a 1D vector and plot it.\n", "import matplotlib.pyplot as plt\n", - " \n", + "\n", "\n", "# Flatten to 1D list\n", "flat = [v for row in evaluation_history for v in row]\n", "\n", "\n", - "\n", "# Plot\n", "plt.figure()\n", - "plt.plot(range(1, len(flat)+1), flat, marker='o')\n", + "plt.plot(range(1, len(flat) + 1), flat, marker=\"o\")\n", "plt.xlabel(\"Evaluation index (flattened)\")\n", "plt.ylabel(\"F value\")\n", "plt.title(\"Evaluation History (Flattened)\")\n", "plt.grid(True)\n", "plt.tight_layout()\n", - "plt.show()\n", - "\n" + "plt.show()" ] } ], diff --git a/test_gp.ipynb b/test_gp.ipynb index 3cadd57..314594b 100644 --- a/test_gp.ipynb +++ b/test_gp.ipynb @@ -15,15 +15,17 @@ "import random\n", "import torch\n", "\n", + "\n", "def set_all_seeds(seed: int = 42):\n", " random.seed(seed)\n", " np.random.seed(seed)\n", " torch.manual_seed(seed)\n", " torch.cuda.manual_seed(seed)\n", - " torch.cuda.manual_seed_all(seed) \n", + " torch.cuda.manual_seed_all(seed)\n", " torch.backends.cudnn.deterministic = True\n", " torch.backends.cudnn.benchmark = False\n", "\n", + "\n", "seed = 42\n", "set_all_seeds(seed)" ] @@ -45,19 +47,23 @@ "source": [ "from sklearn.preprocessing import StandardScaler\n", "from matplotlib import pyplot as plt\n", + "\n", "# l = 6\n", "# g = 3\n", "# para_dict = {'l':l,'g':g}\n", "# total_dataset = QEC_Dataset(l,g,load = True,number = 300,save = True)\n", "total_dataset = MergeDatasets()\n", - "total_dataset.load(['./data/codes/bb_36_6_3_100_depolarizing.pkl',\n", - " './data/codes/bb_36_9_2_100_depolarizing.pkl',\n", - " './data/codes/hgp_36_2_2_3_100_depolarizing.pkl',\n", - " './data/codes/hgp_36_3_3_2_100_depolarizing.pkl'])\n", + "total_dataset.load(\n", + " [\n", + " \"./data/codes/bb_36_6_3_100_depolarizing.pkl\",\n", + " \"./data/codes/bb_36_9_2_100_depolarizing.pkl\",\n", + " \"./data/codes/hgp_36_2_2_3_100_depolarizing.pkl\",\n", + " \"./data/codes/hgp_36_3_3_2_100_depolarizing.pkl\",\n", + " ]\n", + ")\n", "total_dataset.y = torch.tensor(total_dataset.y)\n", "\n", "\n", - "\n", "eps = 1e-20\n", "total_dataset.y = torch.log(total_dataset.y + eps)\n", "\n", @@ -70,18 +76,24 @@ "\n", "\n", "import pandas as pd\n", + "\n", + "\n", "def plot_distribution(y, bin_width):\n", " bins = np.arange(np.min(y), np.max(y) + bin_width, bin_width)\n", - " \n", + "\n", " plt.figure(figsize=(8, 5))\n", - " plt.hist(y, bins=bins, edgecolor='black')\n", - " plt.xlabel('value')\n", - " plt.ylabel('frequency')\n", - " plt.title(f'distribution of y with width={bin_width}')\n", - " plt.grid(axis='y', alpha=0.3)\n", + " plt.hist(y, bins=bins, edgecolor=\"black\")\n", + " plt.xlabel(\"value\")\n", + " plt.ylabel(\"frequency\")\n", + " plt.title(f\"distribution of y with width={bin_width}\")\n", + " plt.grid(axis=\"y\", alpha=0.3)\n", " plt.tight_layout()\n", " plt.show()\n", + "\n", + "\n", "plot_distribution(total_dataset.y.detach().cpu().numpy(), bin_width=0.1)\n", + "\n", + "\n", "# plot_distribution(total_dataset.y, bin_width=0.1)\n", "def train_test_split(dataset, test_size=0.2, shuffle=True, random_state=None):\n", " \"\"\"\n", @@ -109,74 +121,72 @@ "\n", " # Split\n", " train_idx = indices[:n_train]\n", - " test_idx = indices[n_train:]\n", + " test_idx = indices[n_train:]\n", "\n", " # Wrap in Subset\n", " train_dataset = QECSubset(dataset, train_idx)\n", - " test_dataset = QECSubset(dataset, test_idx)\n", + " test_dataset = QECSubset(dataset, test_idx)\n", " return train_dataset, test_dataset\n", + "\n", + "\n", "def evaluate_gp_on_test(\n", " test_data,\n", " model,\n", " *,\n", " plot: bool = True,\n", " return_df: bool = False,\n", - " use_std: bool = False\n", + " use_std: bool = False,\n", "):\n", "\n", " X_test = torch.tensor(test_data.X, dtype=torch.float32)\n", " y_test = torch.tensor(test_data.y, dtype=torch.float32)\n", "\n", - "\n", " model.eval()\n", " model.likelihood.eval()\n", " with torch.no_grad(), gpytorch.settings.fast_pred_var():\n", - "\n", " posterior = model.posterior(X_test)\n", " mean = posterior.mean\n", " var = posterior.variance\n", "\n", - "\n", " err = torch.sqrt(var) if use_std else var\n", "\n", - "\n", " mse = torch.mean((mean - y_test) ** 2).item()\n", " ss_res = torch.sum((y_test - mean) ** 2).item()\n", " ss_tot = torch.sum((y_test - torch.mean(y_test)) ** 2).item()\n", - " r2 = 1 - ss_res / ss_tot if ss_tot > 0 else float('nan')\n", + " r2 = 1 - ss_res / ss_tot if ss_tot > 0 else float(\"nan\")\n", "\n", - " y_test = y_test.squeeze(-1) \n", - " mean = mean.squeeze(-1) \n", - " err = err.squeeze(-1) \n", + " y_test = y_test.squeeze(-1)\n", + " mean = mean.squeeze(-1)\n", + " err = err.squeeze(-1)\n", " idx = torch.argsort(y_test)\n", " y_sorted = y_test[idx].numpy()\n", " m_sorted = mean[idx].numpy()\n", " e_sorted = err[idx].numpy()\n", "\n", - " \n", " if plot:\n", " plt.figure()\n", - " plt.plot(y_sorted, label='True values')\n", - " plt.plot(m_sorted, label='Predicted mean')\n", - " plt.plot(m_sorted + e_sorted, label=f'Mean + {\"STD\" if use_std else \"Var\"}')\n", - " plt.plot(m_sorted - e_sorted, label=f'Mean - {\"STD\" if use_std else \"Var\"}')\n", - " plt.xlabel('Samples (sorted by true value)')\n", - " plt.ylabel('Value')\n", - " plt.title('True vs Predicted mean ± ' + (\"STD\" if use_std else \"VAR\"))\n", + " plt.plot(y_sorted, label=\"True values\")\n", + " plt.plot(m_sorted, label=\"Predicted mean\")\n", + " plt.plot(m_sorted + e_sorted, label=f\"Mean + {'STD' if use_std else 'Var'}\")\n", + " plt.plot(m_sorted - e_sorted, label=f\"Mean - {'STD' if use_std else 'Var'}\")\n", + " plt.xlabel(\"Samples (sorted by true value)\")\n", + " plt.ylabel(\"Value\")\n", + " plt.title(\"True vs Predicted mean ± \" + (\"STD\" if use_std else \"VAR\"))\n", " plt.legend()\n", " plt.tight_layout()\n", " plt.show()\n", "\n", - "\n", - " df = pd.DataFrame({\n", - " 'True value': y_sorted,\n", - " 'Predicted mean': m_sorted,\n", - " 'Predicted ' + ('std' if use_std else 'var'): e_sorted\n", - " })\n", + " df = pd.DataFrame(\n", + " {\n", + " \"True value\": y_sorted,\n", + " \"Predicted mean\": m_sorted,\n", + " \"Predicted \" + (\"std\" if use_std else \"var\"): e_sorted,\n", + " }\n", + " )\n", "\n", " if return_df:\n", - " return mse,r2, df\n", - " return mse,r2\n", + " return mse, r2, df\n", + " return mse, r2\n", "\n", "\n", "# model = get_model(l,g,torch.tensor(train_dataset.X,dtype=torch.float32),torch.tensor(train_dataset.y,dtype=torch.float32))" @@ -230,29 +240,32 @@ "outputs": [], "source": [ "views_info = [\n", - " {\"name\":\"decode\",\n", - " \"partite_classes\":[\"SZ\",\"DQ\",\"SX\"],\n", - " \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"], \n", - " },\n", - " {\"name\":\"xlogic\",\n", - " \"partite_classes\":[\"DQ\",\"SX\",\"LX\"],\n", - " \"relations\":[\"DQ_SX\",\"SX_DQ\",\"LX_DQ\",\"DQ_LX\"],\n", - " },\n", - " {\"name\":\"zlogic\",\n", - " \"partite_classes\":[\"SZ\",\"DQ\",\"LZ\"],\n", - " \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"LZ_DQ\",\"DQ_LZ\"],\n", - " },\n", + " {\n", + " \"name\": \"decode\",\n", + " \"partite_classes\": [\"SZ\", \"DQ\", \"SX\"],\n", + " \"relations\": [\"SZ_DQ\", \"DQ_SZ\", \"DQ_SX\", \"SX_DQ\"],\n", + " },\n", + " {\n", + " \"name\": \"xlogic\",\n", + " \"partite_classes\": [\"DQ\", \"SX\", \"LX\"],\n", + " \"relations\": [\"DQ_SX\", \"SX_DQ\", \"LX_DQ\", \"DQ_LX\"],\n", + " },\n", + " {\n", + " \"name\": \"zlogic\",\n", + " \"partite_classes\": [\"SZ\", \"DQ\", \"LZ\"],\n", + " \"relations\": [\"SZ_DQ\", \"DQ_SZ\", \"LZ_DQ\", \"DQ_LZ\"],\n", + " },\n", "]\n", "# views_info = [\n", "# {\"name\":\"decode\",\n", "# \"partite_classes\":[\"SZ\",\"DQ\",\"SX\"],\n", - "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"], \n", + "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"],\n", "# },\n", "# ]\n", "# views_info = [\n", "# {\"name\":\"decode\",\n", "# \"partite_classes\":[\"SZ\",\"DQ\",\"SX\"],\n", - "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"], \n", + "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"],\n", "# },\n", "# {\"name\":\"xlogic\",\n", "# \"partite_classes\":[\"DQ\",\"SX\",\"LX\"],\n", @@ -270,13 +283,13 @@ "# views_info = [\n", "# {\"name\":\"5-partite\",\n", "# \"partite_classes\":[\"SZ\",\"DQ\",\"SX\",\"LX\",\"LZ\"],\n", - "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\",\"LX_DQ\",\"DQ_LX\",\"LZ_DQ\",\"DQ_LZ\"], \n", + "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\",\"LX_DQ\",\"DQ_LX\",\"LZ_DQ\",\"DQ_LZ\"],\n", "# },\n", "# ]\n", "# views_info = [\n", "# {\"name\":\"decode\",\n", "# \"partite_classes\":[\"SZ\",\"DQ\",\"SX\"],\n", - "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"], \n", + "# \"relations\":[\"SZ_DQ\",\"DQ_SZ\",\"DQ_SX\",\"SX_DQ\"],\n", "# },\n", "# {\"name\":\"xlogic\",\n", "# \"partite_classes\":[\"DQ\",\"SX\",\"LZ\"],\n", @@ -288,11 +301,11 @@ "# },\n", "# ]\n", "# views_info = [\n", - "# # Z-error chain (SZ -> DQ) \n", + "# # Z-error chain (SZ -> DQ)\n", "# {\"name\":\"decode_z\", \"partite_classes\":[\"SZ\",\"DQ\"],\n", "# \"relations\":[\"SZ_DQ\"], },\n", "\n", - "# # X-error chain (DQ -> SX) \n", + "# # X-error chain (DQ -> SX)\n", "# {\"name\":\"decode_x\", \"partite_classes\":[\"DQ\",\"SX\"],\n", "# \"relations\":[\"DQ_SX\"], },\n", "\n", @@ -304,18 +317,19 @@ "# ]\n", "\n", "\n", - "\n", "# An Example of the Encoding\n", - "class E():\n", - " def __init__(self,views_info ):\n", - " self.encoder = CSSEncoder(views_info,mode ='relations')\n", + "class E:\n", + " def __init__(self, views_info):\n", + " self.encoder = CSSEncoder(views_info, mode=\"relations\")\n", "\n", - " def encode_single(self,x):\n", + " def encode_single(self, x):\n", " return self.encoder.encode(x)\n", - " def encode(self,x):\n", + "\n", + " def encode(self, x):\n", " # x: B views\n", " return [self.encode_single(i) for i in x]\n", "\n", + "\n", "e = E(views_info)\n", "e.encode_single(total_dataset.X[0])" ] @@ -327,12 +341,12 @@ "metadata": {}, "outputs": [], "source": [ - "train_dataset, test_dataset = train_test_split(total_dataset,\n", - " test_size=0.2,\n", - " shuffle=True,\n", - " random_state=seed)\n", + "train_dataset, test_dataset = train_test_split(\n", + " total_dataset, test_size=0.2, shuffle=True, random_state=seed\n", + ")\n", "from torch import optim\n", "\n", + "\n", "def train_gpytorch_model(model, train_x, train_y, training_iter=50, lr=0.002):\n", " model.train()\n", " model.likelihood.train()\n", @@ -342,7 +356,7 @@ " history = []\n", " for i in range(training_iter):\n", " optimizer.zero_grad()\n", - " output = model(train_x) # MultivariateNormal over latent f\n", + " output = model(train_x) # MultivariateNormal over latent f\n", " loss = -mll(output, train_y.squeeze(-1))\n", " loss.backward()\n", " optimizer.step()\n", @@ -350,35 +364,41 @@ " plt.plot(history)\n", " return loss.item()\n", "\n", + "\n", "def train_gpytorch_model_seperate(model, train_x, train_y, training_iter=50, lr=0.002):\n", " model.train()\n", " model.likelihood.train()\n", " try:\n", - " nn_parameters = list(model.mean_module.parameters()) + list(model.embed.parameters())\n", + " nn_parameters = list(model.mean_module.parameters()) + list(\n", + " model.embed.parameters()\n", + " )\n", " kernel_parameters = list(model.covar_module.parameters())\n", " likelihood_parameters = list(model.likelihood.parameters())\n", "\n", - "\n", - " optimizer = torch.optim.AdamW([\n", - " {'params': nn_parameters, 'lr': 0.001}, \n", - " {'params': kernel_parameters, 'lr': 0.0001}, \n", - " {'params': likelihood_parameters, 'lr': 0.02} \n", - " ])\n", + " optimizer = torch.optim.AdamW(\n", + " [\n", + " {\"params\": nn_parameters, \"lr\": 0.001},\n", + " {\"params\": kernel_parameters, \"lr\": 0.0001},\n", + " {\"params\": likelihood_parameters, \"lr\": 0.02},\n", + " ]\n", + " )\n", " except:\n", - " nn_parameters = list(model.embed.parameters())\n", + " nn_parameters = list(model.embed.parameters())\n", " kernel_parameters = list(model.covar_module.parameters())\n", " likelihood_parameters = list(model.likelihood.parameters())\n", "\n", - "\n", - " optimizer = torch.optim.AdamW([\n", - " {'params': nn_parameters, 'lr': 0.005}, \n", - " {'params': kernel_parameters, 'lr': 0.001}, \n", - " {'params': likelihood_parameters, 'lr': 0.02} ])\n", + " optimizer = torch.optim.AdamW(\n", + " [\n", + " {\"params\": nn_parameters, \"lr\": 0.005},\n", + " {\"params\": kernel_parameters, \"lr\": 0.001},\n", + " {\"params\": likelihood_parameters, \"lr\": 0.02},\n", + " ]\n", + " )\n", " mll = ExactMarginalLogLikelihood(model.likelihood, model)\n", " history = []\n", " for i in range(training_iter):\n", " optimizer.zero_grad()\n", - " output = model(train_x) # MultivariateNormal over latent f\n", + " output = model(train_x) # MultivariateNormal over latent f\n", " loss = -mll(output, train_y.squeeze(-1))\n", " loss.backward()\n", " optimizer.step()\n", @@ -520,6 +540,8 @@ "import torch.nn as nn\n", "from torch.utils.data import DataLoader\n", "from bayesian_optimization.chaincomplexembedding import ChainComplexEmbedder\n", + "\n", + "\n", "@torch.no_grad()\n", "def show_results(model, data_loader, encoder: E, device: torch.device):\n", " model.eval()\n", @@ -551,8 +573,8 @@ " p_sorted = [t[1] for t in paired]\n", "\n", " plt.figure(figsize=(10, 5))\n", - " plt.plot(y_sorted, label='True')\n", - " plt.plot(p_sorted, label='Predicted')\n", + " plt.plot(y_sorted, label=\"True\")\n", + " plt.plot(p_sorted, label=\"Predicted\")\n", " plt.title(\"Prediction vs True (sorted by True)\")\n", " plt.xlabel(\"Sample Index (sorted)\")\n", " plt.ylabel(\"Value\")\n", @@ -569,21 +591,24 @@ "\n", " print(f\"Test MSE: {mse:.6f}\")\n", " print(f\"R² Score: {R2:.4f}\")\n", + "\n", + "\n", "# 1) Build the embedder (uses the same views_info you defined)\n", "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", "\n", "embedder = ChainComplexEmbedder(\n", " views_info=views_info,\n", - " d_model=128, # you can tune\n", - " num_layers=4, # you can tune\n", + " d_model=128, # you can tune\n", + " num_layers=4, # you can tune\n", " view_aggr=\"sum\",\n", - " num_bases=4, # basis decomposition for relation weights\n", + " num_bases=4, # basis decomposition for relation weights\n", " norm=\"sym\",\n", " residual=True,\n", " self_loop=False,\n", " dropout=0.1,\n", ").to(device)\n", "\n", + "\n", "# 2) Add a small regression head on top of the embedding (B, d_model) -> (B, 1)\n", "class QECRegressor(nn.Module):\n", " def __init__(self, embedder: nn.Module, d_model: int = 128):\n", @@ -596,30 +621,40 @@ " nn.Dropout(0.1),\n", " nn.Linear(128, 1),\n", " )\n", + "\n", " def forward(self, sample_views_list):\n", " z = self.embedder(sample_views_list) # (B, d_model)\n", - " return self.head(z) # (B, 1)\n", + " return self.head(z) # (B, 1)\n", + "\n", "\n", "model = QECRegressor(embedder, d_model=128).to(device)\n", "\n", + "\n", "# 3) Loss and optimizer\n", "class LogCoshLoss(nn.Module):\n", " def forward(self, y_pred, y_true):\n", " # y_pred, y_true: (B,) or (B,1)\n", " diff = y_pred - y_true\n", " # log(cosh(x)) = x + softplus(-2x) - ln(2)\n", - " return torch.mean(diff + torch.nn.functional.softplus(-2.0 * diff) - math.log(2.0))\n", + " return torch.mean(\n", + " diff + torch.nn.functional.softplus(-2.0 * diff) - math.log(2.0)\n", + " )\n", + "\n", "\n", "criterion = LogCoshLoss()\n", "optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)\n", "\n", + "\n", "# 4) Data\n", "def custom_collate(batch):\n", " xs, ys = zip(*batch)\n", " return list(xs), torch.tensor(ys, dtype=torch.float32)\n", "\n", - "train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True,collate_fn=custom_collate)\n", - "test_loader = DataLoader(test_dataset, batch_size=32,collate_fn=custom_collate)\n", + "\n", + "train_loader = DataLoader(\n", + " train_dataset, batch_size=8, shuffle=True, collate_fn=custom_collate\n", + ")\n", + "test_loader = DataLoader(test_dataset, batch_size=32, collate_fn=custom_collate)\n", "\n", "# Your encoder wrapper (already defined in your message)\n", "# para_dict, codeconstructor are already defined as you said.\n", @@ -635,11 +670,13 @@ " for x_batch, y_batch in train_loader:\n", " # x_batch: whatever your dataset yields (e.g., adjacency tensors or parameters)\n", " # encoder.encode(x_batch) -> List[views dict] of length B\n", - " sample_views_list = encoder.encode(x_batch) # DO NOT .to(device); it's a list of dicts\n", - " y_batch = y_batch.to(device).float().view(-1, 1) # (B,1)\n", + " sample_views_list = encoder.encode(\n", + " x_batch\n", + " ) # DO NOT .to(device); it's a list of dicts\n", + " y_batch = y_batch.to(device).float().view(-1, 1) # (B,1)\n", "\n", " optimizer.zero_grad()\n", - " pred = model(sample_views_list) # (B,1)\n", + " pred = model(sample_views_list) # (B,1)\n", " loss = criterion(pred, y_batch)\n", " loss.backward()\n", " optimizer.step()\n", @@ -648,9 +685,9 @@ "\n", " avg = total_loss / len(train_loader)\n", " history.append(avg)\n", - " if epoch%25 == 0 and epoch>=1:\n", - " show_results(model, test_loader, encoder, device)\n", - " print(f\"Epoch {epoch+1:>3d} | Loss: {avg:.6f}\")\n", + " if epoch % 25 == 0 and epoch >= 1:\n", + " show_results(model, test_loader, encoder, device)\n", + " print(f\"Epoch {epoch + 1:>3d} | Loss: {avg:.6f}\")\n", "\n", "plt.figure()\n", "plt.plot(history)\n", @@ -660,6 +697,7 @@ "plt.tight_layout()\n", "plt.show()\n", "\n", + "\n", "# 6) Evaluation helper (MSE / R^2 + a visual check)\n", "@torch.no_grad()\n", "def show_results(model, data_loader, encoder: E, device: torch.device):\n", @@ -692,8 +730,8 @@ " p_sorted = [t[1] for t in paired]\n", "\n", " plt.figure(figsize=(10, 5))\n", - " plt.plot(y_sorted, label='True')\n", - " plt.plot(p_sorted, label='Predicted')\n", + " plt.plot(y_sorted, label=\"True\")\n", + " plt.plot(p_sorted, label=\"Predicted\")\n", " plt.title(\"Prediction vs True (sorted by True)\")\n", " plt.xlabel(\"Sample Index (sorted)\")\n", " plt.ylabel(\"Value\")\n", @@ -711,9 +749,10 @@ " print(f\"Test MSE: {mse:.6f}\")\n", " print(f\"R² Score: {R2:.4f}\")\n", "\n", + "\n", "# 7) Run evaluation on train/test\n", "show_results(model, train_loader, encoder, device)\n", - "show_results(model, test_loader, encoder, device)\n" + "show_results(model, test_loader, encoder, device)" ] }, { @@ -750,31 +789,41 @@ " --> GP mean(z), cov(z)\n", " \"\"\"\n", "\n", - " def __init__(self, train_x, train_y,\n", - " *,\n", - " likelihood: gpytorch.likelihoods.GaussianLikelihood,\n", - " kernel: gpytorch.kernels.Kernel,\n", - " encoder, # callable: encode(X_batch) -> list of views\n", - " embedding: nn.Module, # ChainComplexEmbedder\n", - " mean: str = 'constant',\n", - " mean_input: int = 64):\n", + " def __init__(\n", + " self,\n", + " train_x,\n", + " train_y,\n", + " *,\n", + " likelihood: gpytorch.likelihoods.GaussianLikelihood,\n", + " kernel: gpytorch.kernels.Kernel,\n", + " encoder, # callable: encode(X_batch) -> list of views\n", + " embedding: nn.Module, # ChainComplexEmbedder\n", + " mean: str = \"constant\",\n", + " mean_input: int = 64,\n", + " ):\n", " # train_y must be 1D for ExactGP; we'll squeeze in caller\n", - " super().__init__(train_x, train_y.squeeze(-1) if train_y.dim() == 2 else train_y, likelihood)\n", - " self.encoder = encoder # Python callable (non-differentiable wrt X)\n", - " self.embed = embedding # nn.Module on device\n", + " super().__init__(\n", + " train_x, train_y.squeeze(-1) if train_y.dim() == 2 else train_y, likelihood\n", + " )\n", + " self.encoder = encoder # Python callable (non-differentiable wrt X)\n", + " self.embed = embedding # nn.Module on device\n", " self.embed.to(device)\n", "\n", - " d_model = next(self.embed.parameters()).shape[-1] if hasattr(self.embed, 'parameters') else 128\n", - " d_model = getattr(self.embed, 'd_model', d_model)\n", + " d_model = (\n", + " next(self.embed.parameters()).shape[-1]\n", + " if hasattr(self.embed, \"parameters\")\n", + " else 128\n", + " )\n", + " d_model = getattr(self.embed, \"d_model\", d_model)\n", "\n", " # Mean module\n", - " if mean == 'zero':\n", + " if mean == \"zero\":\n", " self.mean_module = gpytorch.means.ZeroMean()\n", - " elif mean == 'constant':\n", + " elif mean == \"constant\":\n", " self.mean_module = gpytorch.means.ConstantMean()\n", - " elif mean == 'linear':\n", + " elif mean == \"linear\":\n", " self.mean_module = gpytorch.means.LinearMean(input_size=d_model)\n", - " elif mean == 'nn':\n", + " elif mean == \"nn\":\n", " # Small MLP mean head on top of z\n", " self.nn_mean = nn.Sequential(\n", " nn.LayerNorm(d_model),\n", @@ -782,20 +831,22 @@ " nn.GELU(),\n", " nn.Linear(mean_input, 1),\n", " ).to(device)\n", - " # wrap to a Mean that calls nn_mean \n", + "\n", + " # wrap to a Mean that calls nn_mean\n", " class _NNMean(gpytorch.means.Mean):\n", - " def __init__(self, head): \n", - " super().__init__(); self.head = head\n", - " def forward(self, x): \n", + " def __init__(self, head):\n", + " super().__init__()\n", + " self.head = head\n", + "\n", + " def forward(self, x):\n", " return self.head(x).squeeze(-1)\n", + "\n", " self.mean_module = _NNMean(self.nn_mean)\n", " else:\n", " self.mean_module = gpytorch.means.ConstantMean()\n", "\n", - "\n", " self.covar_module = kernel\n", "\n", - "\n", " self._cache_last_inputs = None\n", " self._cache_last_z = None\n", "\n", @@ -804,10 +855,12 @@ " Convert a batch of design vectors X (B, D) to embedding z (B, d_model).\n", " \"\"\"\n", " # Move to CPU numpy for code constructor if needed\n", - " X_np = X.detach().cpu().numpy() if isinstance(X, torch.Tensor) else np.asarray(X)\n", - " sample_views_list = self.encoder(X_np) # list of views (len=B)\n", + " X_np = (\n", + " X.detach().cpu().numpy() if isinstance(X, torch.Tensor) else np.asarray(X)\n", + " )\n", + " sample_views_list = self.encoder(X_np) # list of views (len=B)\n", " # embedder.forward(list) packs internally and returns (B, d_model)\n", - " z = self.embed(sample_views_list) # (B, d_model) on device\n", + " z = self.embed(sample_views_list) # (B, d_model) on device\n", " return z\n", "\n", " def forward(self, X: torch.Tensor) -> MultivariateNormal:\n", @@ -815,34 +868,32 @@ " Return latent f(X) distribution (MVN). GPyTorch's likelihood wraps this to p(y|X).\n", " \"\"\"\n", " X = X.to(device)\n", - " z = self._x_to_z(X) \n", + " z = self._x_to_z(X)\n", " # cache\n", " self._cache_last_inputs = X\n", " self._cache_last_z = z\n", "\n", - " mean_x = self.mean_module(z) \n", - " covar_x = self.covar_module(z) \n", + " mean_x = self.mean_module(z)\n", + " covar_x = self.covar_module(z)\n", " return MultivariateNormal(mean_x, covar_x)\n", "\n", - "\n", " def posterior(self, X: torch.Tensor):\n", - " self.eval(); self.likelihood.eval()\n", + " self.eval()\n", + " self.likelihood.eval()\n", " with torch.no_grad(), gpytorch.settings.fast_pred_var():\n", " # predictive distribution of y\n", " return self.likelihood(self(X.to(device)))\n", "\n", "\n", "# --------- Modeling ---------\n", - "def get_model_( X, y, kernel_type='ard_rbf', mean_type='linear', mean_input=64):\n", - "\n", - "\n", + "def get_model_(X, y, kernel_type=\"ard_rbf\", mean_type=\"linear\", mean_input=64):\n", "\n", " encoder = E(views_info)\n", " embed_dim = 128\n", " # NN embedder\n", " embedding = ChainComplexEmbedder(\n", " views_info=views_info,\n", - " d_model=embed_dim ,\n", + " d_model=embed_dim,\n", " num_layers=4,\n", " view_aggr=\"sum\",\n", " num_bases=4,\n", @@ -852,19 +903,18 @@ " dropout=0.1,\n", " ).to(device)\n", "\n", - "\n", - "\n", " # kernel\n", - " if kernel_type == 'ard_rbf':\n", + " if kernel_type == \"ard_rbf\":\n", " base = RBFKernel(ard_num_dims=embed_dim)\n", " kernel = ScaleKernel(base)\n", - " elif kernel_type == 'matern':\n", + " elif kernel_type == \"matern\":\n", " base = MaternKernel(nu=1.5, ard_num_dims=embed_dim)\n", " kernel = ScaleKernel(base)\n", - " elif kernel_type == 'spectral_mixture':\n", + " elif kernel_type == \"spectral_mixture\":\n", " kernel = SpectralMixtureKernel(num_mixtures=4, ard_num_dims=embed_dim)\n", - " elif kernel_type == 'rbf_plus_periodic':\n", + " elif kernel_type == \"rbf_plus_periodic\":\n", " from gpytorch.kernels import PeriodicKernel\n", + "\n", " rbf = ScaleKernel(RBFKernel(ard_num_dims=embed_dim))\n", " periodic = ScaleKernel(PeriodicKernel(ard_num_dims=embed_dim))\n", " kernel = rbf + periodic\n", @@ -879,7 +929,8 @@ " train_y = y.to(device).float().view(-1)\n", "\n", " gp = GaussianProcess_QEC(\n", - " train_x, train_y,\n", + " train_x,\n", + " train_y,\n", " likelihood=likelihood,\n", " kernel=kernel,\n", " encoder=encoder.encode,\n", @@ -893,45 +944,54 @@ "\n", "# --------- Train ---------\n", "def train_gpytorch_model_separate(model, train_x, train_y, training_iter=100):\n", - " model.train(); model.likelihood.train()\n", + " model.train()\n", + " model.likelihood.train()\n", " # parameter groups\n", " params_embed = list(model.embed.parameters())\n", - " params_kernel = list(model.covar_module.parameters()) if hasattr(model, \"covar_module\") else []\n", + " params_kernel = (\n", + " list(model.covar_module.parameters()) if hasattr(model, \"covar_module\") else []\n", + " )\n", " params_like = list(model.likelihood.parameters())\n", - " params_mean = list(model.mean_module.parameters()) if hasattr(model, \"mean_module\") else []\n", - "\n", - "\n", - " optimizer = torch.optim.AdamW([\n", - " {'params': params_embed, 'lr': 4e-4},\n", - " {'params': params_mean, 'lr': 1e-3},\n", - " {'params': params_kernel, 'lr': 1e-3},\n", - " {'params': params_like, 'lr': 2e-2},\n", - " ])\n", + " params_mean = (\n", + " list(model.mean_module.parameters()) if hasattr(model, \"mean_module\") else []\n", + " )\n", + "\n", + " optimizer = torch.optim.AdamW(\n", + " [\n", + " {\"params\": params_embed, \"lr\": 4e-4},\n", + " {\"params\": params_mean, \"lr\": 1e-3},\n", + " {\"params\": params_kernel, \"lr\": 1e-3},\n", + " {\"params\": params_like, \"lr\": 2e-2},\n", + " ]\n", + " )\n", "\n", " mll = ExactMarginalLogLikelihood(model.likelihood, model)\n", "\n", - "\n", " train_x = train_x.to(device).float()\n", " train_y = train_y.to(device).float().view(-1) # 1D\n", "\n", " history = []\n", " for i in range(training_iter):\n", " optimizer.zero_grad()\n", - " output = model(train_x) # MVN over latent f\n", + " output = model(train_x) # MVN over latent f\n", " loss = -mll(output, train_y)\n", " loss.backward()\n", " optimizer.step()\n", " history.append(loss.item())\n", "\n", - " if (i+1) % max(1, training_iter//10) == 0:\n", - " print(f\"[{i+1:>4d}/{training_iter}] nll={loss.item():.4f}, \"\n", - " f\"noise={model.likelihood.noise.item():.3e}\")\n", + " if (i + 1) % max(1, training_iter // 10) == 0:\n", + " print(\n", + " f\"[{i + 1:>4d}/{training_iter}] nll={loss.item():.4f}, \"\n", + " f\"noise={model.likelihood.noise.item():.3e}\"\n", + " )\n", "\n", " plt.figure()\n", " plt.plot(history)\n", - " plt.xlabel(\"iter\"); plt.ylabel(\"Neg. MLL\")\n", + " plt.xlabel(\"iter\")\n", + " plt.ylabel(\"Neg. MLL\")\n", " plt.title(\"GP training curve\")\n", - " plt.grid(True); plt.tight_layout()\n", + " plt.grid(True)\n", + " plt.tight_layout()\n", " plt.show()\n", " return float(history[-1])\n", "\n", @@ -946,7 +1006,7 @@ " return_df=False,\n", " use_std=True,\n", " plot_mll_hist=True,\n", - " bins=30\n", + " bins=30,\n", "):\n", " import torch\n", " import gpytorch\n", @@ -954,16 +1014,17 @@ " import matplotlib.pyplot as plt\n", "\n", " device = next(model.parameters()).device\n", - " model.eval(); model.likelihood.eval()\n", + " model.eval()\n", + " model.likelihood.eval()\n", "\n", " X_test = torch.tensor(test_data.X, dtype=torch.float32, device=device)\n", " y_test = torch.tensor(test_data.y, dtype=torch.float32, device=device).view(-1)\n", "\n", " with gpytorch.settings.fast_pred_var():\n", " # predictive distribution of y (test predictive)\n", - " pred = model.likelihood(model(X_test)) # MultivariateNormal over y|X\n", - " mean = pred.mean \n", - " var = pred.variance \n", + " pred = model.likelihood(model(X_test)) # MultivariateNormal over y|X\n", + " mean = pred.mean\n", + " var = pred.variance\n", "\n", " # error band for plotting\n", " err = torch.sqrt(var) if use_std else var\n", @@ -972,8 +1033,7 @@ " mse = torch.mean((mean - y_test) ** 2).item()\n", " ss_res = torch.sum((y_test - mean) ** 2).item()\n", " ss_tot = torch.sum((y_test - torch.mean(y_test)) ** 2).item()\n", - " r2 = 1 - ss_res / ss_tot if ss_tot > 0 else float('nan')\n", - "\n", + " r2 = 1 - ss_res / ss_tot if ss_tot > 0 else float(\"nan\")\n", "\n", " # Use univariate Normal with mean_i, var_i\n", " eps = 1e-12\n", @@ -981,30 +1041,26 @@ " normal = torch.distributions.Normal(loc=mean, scale=std)\n", "\n", " # per-point log-likelihood and density\n", - " per_point_logpdf = normal.log_prob(y_test) \n", - " per_point_pdf = torch.exp(per_point_logpdf) \n", - "\n", + " per_point_logpdf = normal.log_prob(y_test)\n", + " per_point_pdf = torch.exp(per_point_logpdf)\n", "\n", - " z = (y_test - mean) / std \n", + " z = (y_test - mean) / std\n", " z_mean = z.mean().item()\n", - " z_std = z.std(unbiased=True).item()\n", - " z_mae = z.abs().mean().item()\n", + " z_std = z.std(unbiased=True).item()\n", + " z_mae = z.abs().mean().item()\n", " z_within_1 = (z.abs() <= 1.0).float().mean().item()\n", " z_within_2 = (z.abs() <= 2.0).float().mean().item()\n", " z_within_3 = (z.abs() <= 3.0).float().mean().item()\n", "\n", - "\n", " # summary stats of per-point densities\n", " avg_pdf = per_point_pdf.mean().item()\n", " avg_logpdf = per_point_logpdf.mean().item()\n", "\n", - "\n", " total_loglik = pred.log_prob(y_test).item()\n", - " avg_loglik = total_loglik / y_test.numel()\n", - "\n", + " avg_loglik = total_loglik / y_test.numel()\n", "\n", " total_nll = -total_loglik\n", - " avg_nll = -avg_loglik\n", + " avg_nll = -avg_loglik\n", " per_point_nll = (-per_point_logpdf).mean().item()\n", "\n", " # ----- Sorting for line-plot -----\n", @@ -1014,52 +1070,62 @@ " e_sorted = err[idx].detach().cpu().numpy()\n", "\n", " if plot:\n", - " plt.figure(figsize=(10,5))\n", - " plt.plot(y_sorted, label='True')\n", - " plt.plot(m_sorted, label='Predicted mean')\n", - " plt.plot(m_sorted + e_sorted, label=('Mean + STD' if use_std else 'Mean + Var'))\n", - " plt.plot(m_sorted - e_sorted, label=('Mean - STD' if use_std else 'Mean - Var'))\n", + " plt.figure(figsize=(10, 5))\n", + " plt.plot(y_sorted, label=\"True\")\n", + " plt.plot(m_sorted, label=\"Predicted mean\")\n", + " plt.plot(m_sorted + e_sorted, label=(\"Mean + STD\" if use_std else \"Mean + Var\"))\n", + " plt.plot(m_sorted - e_sorted, label=(\"Mean - STD\" if use_std else \"Mean - Var\"))\n", " plt.xlabel(\"Samples (sorted by true)\")\n", " plt.ylabel(\"Value\")\n", - " plt.legend(); plt.grid(True); plt.tight_layout(); plt.show()\n", + " plt.legend()\n", + " plt.grid(True)\n", + " plt.tight_layout()\n", + " plt.show()\n", "\n", " if plot_mll_hist:\n", " pdf_np = per_point_pdf.detach().cpu().numpy()\n", " logpdf_np = per_point_logpdf.detach().cpu().numpy()\n", "\n", - " plt.figure(figsize=(10,4))\n", - " plt.subplot(1,2,1)\n", + " plt.figure(figsize=(10, 4))\n", + " plt.subplot(1, 2, 1)\n", " plt.hist(pdf_np, bins=bins, alpha=0.85)\n", " plt.title(\"Per-point predictive density p(y_i|X)\")\n", - " plt.xlabel(\"density value\"); plt.ylabel(\"count\"); plt.grid(True)\n", + " plt.xlabel(\"density value\")\n", + " plt.ylabel(\"count\")\n", + " plt.grid(True)\n", "\n", - " plt.subplot(1,2,2)\n", + " plt.subplot(1, 2, 2)\n", " plt.hist(logpdf_np, bins=bins, alpha=0.85)\n", " plt.title(\"Per-point predictive log-likelihood\")\n", - " plt.xlabel(\"log density\"); plt.ylabel(\"count\"); plt.grid(True)\n", - " plt.tight_layout(); plt.show()\n", + " plt.xlabel(\"log density\")\n", + " plt.ylabel(\"count\")\n", + " plt.grid(True)\n", + " plt.tight_layout()\n", + " plt.show()\n", "\n", " # dataframe for inspection\n", - " df = pd.DataFrame({\n", - " 'True value': y_sorted,\n", - " 'Predicted mean': m_sorted,\n", - " ('std' if use_std else 'var'): e_sorted\n", - " })\n", + " df = pd.DataFrame(\n", + " {\n", + " \"True value\": y_sorted,\n", + " \"Predicted mean\": m_sorted,\n", + " (\"std\" if use_std else \"var\"): e_sorted,\n", + " }\n", + " )\n", "\n", " extras = {\n", - " 'avg_per_point_pdf': avg_pdf,\n", - " 'avg_per_point_logpdf': avg_logpdf,\n", - " 'total_multivariate_loglik': total_loglik,\n", - " 'avg_multivariate_loglik_per_point': avg_loglik,\n", - " 'total_multivariate_nll': total_nll,\n", - " 'avg_multivariate_nll_per_point': avg_nll,\n", - " 'avg_per_point_nll_independent': per_point_nll,\n", - " 'z_mean': z_mean,\n", - " 'z_std': z_std,\n", - " 'z_mae': z_mae,\n", - " 'coverage_within_1sigma': z_within_1, \n", - " 'coverage_within_2sigma': z_within_2, \n", - " 'coverage_within_3sigma': z_within_3, \n", + " \"avg_per_point_pdf\": avg_pdf,\n", + " \"avg_per_point_logpdf\": avg_logpdf,\n", + " \"total_multivariate_loglik\": total_loglik,\n", + " \"avg_multivariate_loglik_per_point\": avg_loglik,\n", + " \"total_multivariate_nll\": total_nll,\n", + " \"avg_multivariate_nll_per_point\": avg_nll,\n", + " \"avg_per_point_nll_independent\": per_point_nll,\n", + " \"z_mean\": z_mean,\n", + " \"z_std\": z_std,\n", + " \"z_mae\": z_mae,\n", + " \"coverage_within_1sigma\": z_within_1,\n", + " \"coverage_within_2sigma\": z_within_2,\n", + " \"coverage_within_3sigma\": z_within_3,\n", " }\n", " print(extras)\n", "\n", @@ -1068,12 +1134,11 @@ " return mse, r2, extras\n", "\n", "\n", - "\n", "model = get_model_(\n", " train_dataset.X,\n", " torch.tensor(train_dataset.y, dtype=torch.float32),\n", - " kernel_type='matern',\n", - " mean_type='linear',\n", + " kernel_type=\"matern\",\n", + " mean_type=\"linear\",\n", " mean_input=128,\n", ")\n", "\n", @@ -1081,16 +1146,20 @@ " model,\n", " torch.tensor(train_dataset.X, dtype=torch.float32),\n", " torch.tensor(train_dataset.y, dtype=torch.float32),\n", - " training_iter=200\n", + " training_iter=200,\n", ")\n", "\n", - "mse1, r21, extra1 = evaluate_gp_on_test(train_dataset, model, plot=True, return_df=False, use_std=True)\n", + "mse1, r21, extra1 = evaluate_gp_on_test(\n", + " train_dataset, model, plot=True, return_df=False, use_std=True\n", + ")\n", "print(f\"Train MSE: {mse1:.6f} | Train R2: {r21:.6f}\")\n", "# print(df_tr.head())\n", "\n", - "mse2, r22, extra2 = evaluate_gp_on_test(test_dataset, model, plot=True, return_df=False, use_std=True)\n", + "mse2, r22, extra2 = evaluate_gp_on_test(\n", + " test_dataset, model, plot=True, return_df=False, use_std=True\n", + ")\n", "print(f\"Test MSE: {mse2:.6f} | Test R2: {r22:.6f}\")\n", - "# print(df_te.head())\n" + "# print(df_te.head())" ] }, { From 36c6c95bbea4b5c4be48d6a27af09eb18d2f8b9d Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Fri, 20 Feb 2026 12:18:26 +0000 Subject: [PATCH 03/36] added gbb construct method to CodeConstructor, added method to generate gbb vector in Get_new_points_function class --- BOplayground.py | 36 +++++++++++++++++- .../code_construction.cpython-310.pyc | Bin 0 -> 17163 bytes .../code_construction.cpython-311.pyc | Bin 0 -> 35946 bytes code_construction/code_construction.py | 34 ++++++++++++++++- 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 code_construction/__pycache__/code_construction.cpython-310.pyc create mode 100644 code_construction/__pycache__/code_construction.cpython-311.pyc diff --git a/BOplayground.py b/BOplayground.py index fdb07a4..be22885 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -929,10 +929,13 @@ def set_all_seeds(seed: int = 42): class Get_new_points_function: - def __init__(self, method="qc-ldpc-hgp", code_constructor=None, encode="None"): + def __init__( + self, method="qc-ldpc-hgp", code_constructor=None, encode="None", density=None + ): self.method = method self.code_constructor = code_constructor self.encode = encode + self.density = density self.init = False def get_new_points_function(self, number): @@ -940,6 +943,8 @@ def get_new_points_function(self, number): new_points = self.get_new_points_HGP(number) elif self.method == "bb": new_points = self.get_new_bb_vector(number) + elif self.method == "gbb": + new_points = self.get_new_gbb_vector(number) return new_points def get_new_points_HGP(self, number): @@ -976,6 +981,35 @@ def get_new_bb_vector(self, number): number -= 1 return np.array(results) + def get_new_gbb_vector(self, number): + results = [] + l = self.code_constructor.para_dict["l"] + g = self.code_constructor.para_dict["g"] + + while number > 0: + new_point = np.zeros(shape=(2 * l * g)) + new_point[0] = 1 # fix x^0 * y^0 = 1 to ignore identical shifted codes + + indexes = np.random.shuffle([i for i in range(l * g)]) + for i in range( + self.density - 1 + ): # density: number of 1s in both A and B, fixed to ensure sparsity + new_point[indexes[i]] = 1 + + indexes = np.random.shuffle(indexes) + for i in range(self.density): + new_point[indexes[i] + l * g] = 1 + + c = self.code_constructor.construct( + new_point + ) # TODO define code_constructor.construct for GBB + if c.k == 0: + continue + else: + results.append(new_point) + number -= 1 + return np.array(results) + if __name__ == "__main__": import pickle diff --git a/code_construction/__pycache__/code_construction.cpython-310.pyc b/code_construction/__pycache__/code_construction.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68c563b233df08c4bc8cd5e95368e3838cd27666 GIT binary patch literal 17163 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 literal 0 HcmV?d00001 diff --git a/code_construction/__pycache__/code_construction.cpython-311.pyc b/code_construction/__pycache__/code_construction.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b76101e544850ba62f6fad2f92b505f856140212 GIT binary patch literal 35946 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 literal 0 HcmV?d00001 diff --git a/code_construction/code_construction.py b/code_construction/code_construction.py index 24d4f6d..1ff10f9 100644 --- a/code_construction/code_construction.py +++ b/code_construction/code_construction.py @@ -94,8 +94,14 @@ def check_parameters_validity(self): # return self.check_bivariate_bycicle_parameters_validity() elif self.method == "rotated-surface": return True - elif self.method in ["qc-gb", "bivariate-bycicle", "bb", "symmetric-qc-gb"]: - if self.method in ["bb", "bivariate-bycicle"]: + elif self.method in [ + "qc-gb", + "bivariate-bycicle", + "bb", + "symmetric-qc-gb", + "gbb", + ]: + if self.method in ["bb", "bivariate-bycicle", "gbb"]: try: self.n = self.para_dict["l"] * self.para_dict["g"] self.nx = self.n @@ -131,6 +137,8 @@ def construct(self, parameters): return self.quasi_cyclic_generalized_bicycle_code(parameters) elif self.method == "bivariate-bycicle" or self.method == "bb": return self.arbitrary_bivariate_bicycle_code(parameters) + elif self.method == "gbb": + return self.generalised_bivariate_bicycle_code(parameters) elif self.method == "symmetric-qc-gb": return self.symmetric_quasi_cyclic_generalized_bicycle_code(parameters) else: @@ -597,6 +605,28 @@ def arbitrary_bivariate_bicycle_code(self, parameters): A[i][i][j + 1] = (A[i][i][j + 1] + 1) % 2 if b[j + l] == 1: B[i][i][j + 1] = (B[i][i][j + 1] + 1) % 2 + + return self.quasi_cyclic_generalized_bicycle_code({"A": A, "B": B}) + + def generalised_bivariate_bicycle_code(self, parameters): + l = self.para_dict["l"] + g = self.para_dict["g"] + + a = parameters[: (l * g)] + b = parameters[(l * g) :] + + A = np.zeros((l, l, g), dtype=int) + B = np.zeros((l, l, g), dtype=int) + + for i in range(l): + for j in range(l): + for k in range(g): + idx = (j * g) + k + if a[idx] == 1: + A[i][(i + j) % l][k] = 1 + if b[idx] == 1: + B[i][(i + j) % l][k] = 1 + return self.quasi_cyclic_generalized_bicycle_code({"A": A, "B": B}) def symmetric_quasi_cyclic_generalized_bicycle_code(self, A): From abccb17646867bb529dea6aa836069389d3c5f8d Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Wed, 25 Feb 2026 12:43:21 +0000 Subject: [PATCH 04/36] hill climbing now swaps rather than flips bits for gbb codes, modified main to be more flexible with code types and added initial datapoint generation --- BOplayground.py | 132 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 103 insertions(+), 29 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index be22885..5270c31 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -2,6 +2,7 @@ # add more BO experiments; device = "cuda" DEVICE = "cuda" +import os import random import numpy as np import torch @@ -25,7 +26,13 @@ class HillClimbing: def __init__( - self, next_points_num, gnp, acquisition, device="cuda", validator=None + self, + next_points_num, + gnp, + acquisition, + device="cuda", + validator=None, + method="bb", ): self.next_points_num = int(next_points_num) @@ -35,9 +42,15 @@ def __init__( self.validator = ( validator # 例如 lambda z: code_constructor.construct(z).k != 0 ) - - def hill_climbing_neighbors(self, x: torch.Tensor) -> torch.Tensor: - + match method: + case "gbb": + self.mutate = self.swap_neighbours + case "bb": + self.mutate = self.flip_neighbours + case _: + raise ValueError("The method is not supported.") + + def flip_neighbours(self, x: torch.Tensor) -> torch.Tensor: x = x.detach() d = x.numel() neigh_list = [] @@ -48,6 +61,50 @@ def hill_climbing_neighbors(self, x: torch.Tensor) -> torch.Tensor: neigh_list.append(n) return torch.stack(neigh_list, dim=0) # [d, d] + # allows swapping between A and B + def swap_neighbours(self, x: torch.Tensor) -> torch.Tensor: + x = x.detach() + + ones_indices = torch.where(x[1:] == 1.0)[0] + 1 # dont swap anchor 1 at [0,0] + zeros_indices = torch.where(x == 0)[0] + + neigh_list = [] + for i in ones_indices: + for j in zeros_indices: + n = x.clone() + n[i] == 0.0 + n[j] == 1.0 + neigh_list.append(n) + return torch.stack(neigh_list, dim=0) + + # only swaps within A and B so that both matrices always have the same number of 1s + def swap_neighbours_within_matrices(self, x: torch.Tensor) -> torch.Tensor: + x = x.detach() + half_length = x.size()[0] // 2 + + ones_indices_A = torch.where(x[1:half_length] == 1.0)[0] + 1 + zeros_indices_A = torch.where(x[:half_length] == 0)[0] + + neigh_list = [] + for i in ones_indices_A: + for j in zeros_indices_A: + n = x.clone() + n[i] == 0.0 + n[j] == 1.0 + neigh_list.append(n) + + ones_indices_B = torch.where(x[half_length:] == 1.0)[0] + zeros_indices_B = torch.where(x[half_length:] == 0)[0] + + for i in ones_indices_B: + for j in zeros_indices_B: + n = x.clone() + n[i] == 0.0 + n[j] == 1.0 + neigh_list.append(n) + + return torch.stack(neigh_list, dim=0) + @torch.no_grad() def __call__(self, gp) -> torch.Tensor: @@ -61,7 +118,7 @@ def __call__(self, gp) -> torch.Tensor: best_val = self.acquisition(best_neighbor.unsqueeze(0), gp).reshape(-1)[0] while True: - nbrs = self.hill_climbing_neighbors(best_neighbor).to(self.device) + nbrs = self.mutate(best_neighbor).to(self.device) acq_vals = self.acquisition(nbrs, gp).reshape(-1) # [d] top_val, top_idx = torch.topk(acq_vals, k=1) top_val = top_val[0] @@ -84,9 +141,7 @@ def __call__(self, gp) -> torch.Tensor: tries = 0 while not self.validator(cand_np[i]): tries += 1 - cand_np[i] = np.random.randint( - 0, 2, cand_np[i].shape, dtype=np.int64 - ) + cand_np[i] = self.gnp(1) if tries > 1000: break cand = torch.tensor(cand_np, dtype=torch.float32, device=self.device) @@ -1000,9 +1055,7 @@ def get_new_gbb_vector(self, number): for i in range(self.density): new_point[indexes[i] + l * g] = 1 - c = self.code_constructor.construct( - new_point - ) # TODO define code_constructor.construct for GBB + c = self.code_constructor.construct(new_point) if c.k == 0: continue else: @@ -1059,22 +1112,40 @@ def get_new_gbb_vector(self, number): # y,pl = obj_func(x) # y_init.append(y) # pl_init.append(pl) - if l == 6 and g == 3: - if lambda_ == 1: - init_data_file = ( - f"./data/BO_initial_points/BO_initial_points_{dataset_index}_1.0_63.pkl" - ) - else: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}_63.pkl" + if code_class == "gbb": + init_data_file = "./data/BO_initial_points/GBB_BO_initial_points_{dataset_index}_{lambda_}_{l}_{g}.pkl" + else: # bb + if l == 6 and g == 3: + if lambda_ == 1: + init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_1.0_63.pkl" + else: + init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}_63.pkl" - else: - if lambda_ == 1: - init_data_file = ( - f"./data/BO_initial_points/BO_initial_points_{dataset_index}_1.0.pkl" - ) else: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" - # file with 63 suffix has (l,m)=(6,3). Otherwise (l,m)=(12,6) + if lambda_ == 1: + init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_1.0.pkl" + else: + init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" + # file with 63 suffix has (l,m)=(6,3). Otherwise (l,m)=(12,6) + + if not os.path.exists(init_data_file): + # no starting codes exist, so generate some: + init_num = 20 # TODO check pkls see how big they are + print(f"Generating and evaluating {init_num} initial {code_class} codes...") + X_init = gnp(init_num) + y_init = [] + pl_init = [] + for i, x in enumerate(X_init): + y, pl = obj_func(x) + y_init.append(y) + pl_init.append(pl) + print("Evaluated initial code {i}/{init_num} (score={y:.4f})") + + with open(init_data_file, "wb") as f: + pickle.dump({"X": X_init, "y": y_init, "pl": pl_init}, f) + print(f"Saved initial points to {init_data_file}") + + print(f"Loading initial codes from {init_data_file}...") with open(init_data_file, "rb") as f: data = pickle.load(f) X_init = data["X"] @@ -1137,7 +1208,7 @@ def get_new_gbb_vector(self, number): ) # hill climbing: - def bb_validator(candidate_np): + def code_validator(candidate_np): return code_constructor.construct(candidate_np).k != 0 next_points_num = 4 @@ -1147,8 +1218,10 @@ def bb_validator(candidate_np): gnp=gnp, # get new points function acquisition=acq, device=DEVICE, - validator=bb_validator, + validator=code_validator, + method=code_class, ) + # assemble BO bo_iterations = 50 bo = BO_on_QEC( @@ -1161,7 +1234,7 @@ def bb_validator(candidate_np): initial_pl=pl_init, initial_y=y_init, BO_iterations=bo_iterations, - description="BB-BO (GP+EI+HC)", + description=f"{code_class.upper()}-BO (GP+EI+HC)", device=DEVICE, pretrain=True, ) @@ -1173,7 +1246,8 @@ def bb_validator(candidate_np): flat = y_init_list + flat with open( - f"./data/BO_results/BO_{l}_{g}_{dataset_index}_{seed}_{lambda_}.pkl", "wb" + f"./data/BO_results/{code_class}_BO_{l}_{g}_{dataset_index}_{seed}_{lambda_}.pkl", + "wb", ) as f: results = {"best_x": best_x, "best_y": best_y, "evaluation_history": flat} pickle.dump(results, f) From 6f2a082d8a1360864bd74da9756dccc1c8570e88 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Wed, 25 Feb 2026 13:05:32 +0000 Subject: [PATCH 05/36] allowed code type to be optionally passed as the first arg to BOplayground.py --- BOplayground.py | 23 +++++++++++------- .../code_construction.cpython-310.pyc | Bin 17163 -> 0 bytes .../code_construction.cpython-311.pyc | Bin 35946 -> 0 bytes 3 files changed, 14 insertions(+), 9 deletions(-) delete mode 100644 code_construction/__pycache__/code_construction.cpython-310.pyc delete mode 100644 code_construction/__pycache__/code_construction.cpython-311.pyc diff --git a/BOplayground.py b/BOplayground.py index 5270c31..a1f9cda 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -1068,14 +1068,20 @@ def get_new_gbb_vector(self, number): import pickle import sys - if len(sys.argv) == 3: - seed = int(sys.argv[1]) - dataset_index = int(sys.argv[2]) + code_class = "bb" + + args = sys.argv[1:] + if args[0].isalpha(): + code_class = args.pop(0) + + if len(args) == 3: + seed = int(args[1]) + dataset_index = int(args[2]) lambda_ = 1 - elif len(sys.argv) > 3: - seed = int(sys.argv[1]) - dataset_index = int(sys.argv[2]) - lambda_ = float(sys.argv[3]) + elif len(args) > 3: + seed = int(args[1]) + dataset_index = int(args[2]) + lambda_ = float(args[3]) else: seed = 42 dataset_index = 0 @@ -1089,7 +1095,6 @@ def get_new_gbb_vector(self, number): ) para_dict = {"l": l, "g": g} - code_class = "bb" code_constructor = CodeConstructor(method=code_class, para_dict=para_dict) # define objective function @@ -1145,7 +1150,7 @@ def get_new_gbb_vector(self, number): pickle.dump({"X": X_init, "y": y_init, "pl": pl_init}, f) print(f"Saved initial points to {init_data_file}") - print(f"Loading initial codes from {init_data_file}...") + print(f"Loading initial codes from {init_data_file}") with open(init_data_file, "rb") as f: data = pickle.load(f) X_init = data["X"] diff --git a/code_construction/__pycache__/code_construction.cpython-310.pyc b/code_construction/__pycache__/code_construction.cpython-310.pyc deleted file mode 100644 index 68c563b233df08c4bc8cd5e95368e3838cd27666..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17163 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 diff --git a/code_construction/__pycache__/code_construction.cpython-311.pyc b/code_construction/__pycache__/code_construction.cpython-311.pyc deleted file mode 100644 index b76101e544850ba62f6fad2f92b505f856140212..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35946 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 From bb6521d7f9b24156f22b2e7972389ba8d525a4e9 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Wed, 25 Feb 2026 20:02:04 +0000 Subject: [PATCH 06/36] bug fixes: gbb codes now run --- BOplayground.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index a1f9cda..06f66b4 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -1,5 +1,3 @@ -# Todo: -# add more BO experiments; device = "cuda" DEVICE = "cuda" import os @@ -1045,13 +1043,13 @@ def get_new_gbb_vector(self, number): new_point = np.zeros(shape=(2 * l * g)) new_point[0] = 1 # fix x^0 * y^0 = 1 to ignore identical shifted codes - indexes = np.random.shuffle([i for i in range(l * g)]) + indexes = np.random.permutation(l * g) for i in range( self.density - 1 ): # density: number of 1s in both A and B, fixed to ensure sparsity new_point[indexes[i]] = 1 - indexes = np.random.shuffle(indexes) + np.random.shuffle(indexes) for i in range(self.density): new_point[indexes[i] + l * g] = 1 @@ -1068,7 +1066,9 @@ def get_new_gbb_vector(self, number): import pickle import sys + # default values code_class = "bb" + density = 3 args = sys.argv[1:] if args[0].isalpha(): @@ -1078,10 +1078,15 @@ def get_new_gbb_vector(self, number): seed = int(args[1]) dataset_index = int(args[2]) lambda_ = 1 - elif len(args) > 3: + elif len(args) == 4: seed = int(args[1]) dataset_index = int(args[2]) lambda_ = float(args[3]) + elif len(args) == 5: + seed = int(args[1]) + dataset_index = int(args[2]) + lambda_ = float(args[3]) + density = int(args[4]) else: seed = 42 dataset_index = 0 @@ -1106,7 +1111,7 @@ def get_new_gbb_vector(self, number): pl_to_obj = Obj_Func.pl_to_obj_with_std # method of sampling new points gnp = Get_new_points_function( - method=code_class, code_constructor=code_constructor + method=code_class, code_constructor=code_constructor, density=density ).get_new_points_function # initial points: # init_num = 20 @@ -1118,7 +1123,7 @@ def get_new_gbb_vector(self, number): # y_init.append(y) # pl_init.append(pl) if code_class == "gbb": - init_data_file = "./data/BO_initial_points/GBB_BO_initial_points_{dataset_index}_{lambda_}_{l}_{g}.pkl" + init_data_file = f"./data/BO_initial_points/GBB_BO_initial_points_{dataset_index}_{lambda_}_{density}_{l}_{g}.pkl" else: # bb if l == 6 and g == 3: if lambda_ == 1: @@ -1135,7 +1140,7 @@ def get_new_gbb_vector(self, number): if not os.path.exists(init_data_file): # no starting codes exist, so generate some: - init_num = 20 # TODO check pkls see how big they are + init_num = 20 print(f"Generating and evaluating {init_num} initial {code_class} codes...") X_init = gnp(init_num) y_init = [] @@ -1144,7 +1149,7 @@ def get_new_gbb_vector(self, number): y, pl = obj_func(x) y_init.append(y) pl_init.append(pl) - print("Evaluated initial code {i}/{init_num} (score={y:.4f})") + print(f"Evaluated initial code {i}/{init_num} (score={y:.4f})") with open(init_data_file, "wb") as f: pickle.dump({"X": X_init, "y": y_init, "pl": pl_init}, f) From 04fd891ddf4ccd6fcb1e404f8873b76bd71f4d96 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Fri, 6 Mar 2026 10:44:37 +0000 Subject: [PATCH 07/36] results from first GBB experiment, plus notebook to view results --- .../GBB_BO_initial_points_0_1_3_12_6.pkl | Bin 0 -> 23577 bytes data/BO_results/gbb_BO_12_6_0_42_1.pkl | Bin 0 -> 3286 bytes read_results.ipynb | 74 ++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 data/BO_initial_points/GBB_BO_initial_points_0_1_3_12_6.pkl create mode 100644 data/BO_results/gbb_BO_12_6_0_42_1.pkl create mode 100644 read_results.ipynb diff --git a/data/BO_initial_points/GBB_BO_initial_points_0_1_3_12_6.pkl b/data/BO_initial_points/GBB_BO_initial_points_0_1_3_12_6.pkl new file mode 100644 index 0000000000000000000000000000000000000000..30d12ccf9bb75add88d2090fd6fe7c5bcb73fdf2 GIT binary patch literal 23577 zcmeI4d2CEU9LINCjS4}8P%4s2Xo89e8rMiGCM+97qpCuWRh1r_wM}}cE3V3u5F?22 z7jg8^qV8l#svcUzys z15+(spHdVnS(A}8EUzHfSrnI%?{dWD7Ua0I(_F5!A|wr1>vCk|=WTMk3Nqa25RxLg z5|Pw5FVmND2-z&VP@***?Lo^?tj%g0YCEtSZHSN}GTlW^M`uyk`gl~f*B1|$th15b zZeP@aKV5%p7B@;SOR%K)Bv~xqK=-fze=E)Ve;55lO)mP|DNRSBzmIPH-QP6XboOG^ zm$pv1etPjC=Tj?j+Pnkp7ihj#xbfDZbxy4HvF^`mpQ7q!#e-E}t#H;A#|7PU?-Pqp>DxrwHiHy_&bs#;s)bb(gOo_ntf{jHo%xp?Rum6(g_uX^P2>&N&$y$WgpjhbfZjT=jbLuhlG&U@(T^g1QJBVOJw%X-?3tD1*5ttjm0gr2uiGrYyEG599xU5EYXZJd03PQ9ag zC%y@vHm^PDHU6>K5_W9r5S;ure9G>70N+&XZ@tz3fI|v%=5&b%H`?7P+pLG{S# zEuW(k@sA%R7ysPNh2Z-9toYHT9tbLrv`yIf;W-X9Tpga=5(UAfSDjZMA-Fa;eaVyW z=OL&_oOrspFb5~^^Ire8>M{i9nqwZnUzY$uZT~^zZ%%H7peprXgMHQm29m6YA=}LQtNW5?v8F1((0rntr9K6oLyit5?n~pANyJ>`on9AgCGlsqk6*Pe(!A EUkRy-H~;_u literal 0 HcmV?d00001 diff --git a/data/BO_results/gbb_BO_12_6_0_42_1.pkl b/data/BO_results/gbb_BO_12_6_0_42_1.pkl new file mode 100644 index 0000000000000000000000000000000000000000..7734384f52e52afb3f795af43e0dd0d3fc7c6d45 GIT binary patch literal 3286 zcmdUwYitx%6vub_prwU?K#)>QhKJaq)KW_=w(5$N0xKdAk_``cx7&d(EW4fEX{9P; zFsZS%Xwea403Cb;QWXIsG)l^V5<)NmjXJyc&bHHPn-CQ70|W%YJLl|74Iv>R(S-f5 z_m}_woclg^q>Q&u4s*b7gIv%yq=t`)-t}_ZFfkGh)D?Lf#9$~Uw~g{f`I?4csMag; z;aDW6UC-_-Wvx=2(MGTmt&HlQOI7KI`13{QsHMR{jC%nH;H!oi`rQ@{-nn(y8ZT6ecZ~w{gLmi%>R2I{;Tl@`<0vJ#j@Ms zZf};|&Str#EteHah!2RYPFQW`E{xXlQNFe+7!X;x6)u)V7N08RCb=%7pe}2+!+Gb{ zAgjOPl2TTQ#W&WV^CmU;uV_|-Y}xSpq@$xNwU32`Ep;QTT^hwV`a=zVb1S`dLGw{) zl2^(F)rZNZax<~|u=f2t6@czA6aX?OUqZO_4FZ+lLx`X4Q@~1Gk0FpFLw%r-iya)o z(me`*{#}a?fZiQP=*@fBKqc>2*0RybwntS|(r3C5o@R|{_4cN_0O+Ti4FHlX01_oojwwSr%#Bp`x*u^!2>U1T#IKQ_$LZfdinM22_A#@ni&YV5kceW7~ZS zfL2>!0Cewt(g%>}+^GQQdDBpdYa1m{Nai~!I9;uxVqMxkBaD`IFlm5AL)Ux=(V_VEtrYQR^)9vLyysq(7BDc(Fg5rlMsP2)zJG8aV*6 z?;HD z3M)||$ATPKbZ?0Q5U(;@Gu`?q*yTy*PmsWh_Z>jcS1fmdLac{+@RM}n7<0^i+BO;p zm#(=0w69M|s92So$j?+kv7aX6?ATPyU6T~B65jDX0Aigo@lH2DF-{5!T4R?<2v!#P zJYay59FxbPCC*n7uIKm=j;CG#ZElMTmCZX5lF$9X1hxvAf6hQC+pRF*8;bq`({{9I literal 0 HcmV?d00001 diff --git a/read_results.ipynb b/read_results.ipynb new file mode 100644 index 0000000..8f37962 --- /dev/null +++ b/read_results.ipynb @@ -0,0 +1,74 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "0", + "metadata": {}, + "outputs": [], + "source": [ + "import pickle\n", + "import numpy as np\n", + "\n", + "file_path = \"./data/BO_results/gbb_BO_12_6_0_42_1.pkl\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "print(f\"Loading results from: {file_path}\\n\")\n", + "\n", + "with open(file_path, \"rb\") as f:\n", + " results = pickle.load(f)\n", + "\n", + "best_x = results[\"best_x\"]\n", + "best_y = results[\"best_y\"]\n", + "history = results[\"evaluation_history\"]\n", + "\n", + "print(f\"Best Score (y): {best_y}\") \n", + "print(f\"Binary Vector:\\n{best_x}\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "if len(history) > 10:\n", + " print(f\"First 5 scores: {[round(num, 4) for num in history[:5]]}\")\n", + " print(f\"Last 5 scores: {[round(num, 4) for num in history[-5:]]}\")\n", + "else:\n", + " print(history)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qec", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.19" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From eb213517f1409869746872aec92d9b37932bcea9 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Wed, 25 Mar 2026 16:13:11 +0000 Subject: [PATCH 08/36] added function to generate gb codes from a list of parameters --- .../code_construction.cpython-310.pyc | Bin 0 -> 17715 bytes .../code_construction.cpython-311.pyc | Bin 0 -> 35946 bytes code_construction/code_construction.py | 53 ++++++++++++++++-- 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 code_construction/__pycache__/code_construction.cpython-310.pyc create mode 100644 code_construction/__pycache__/code_construction.cpython-311.pyc diff --git a/code_construction/__pycache__/code_construction.cpython-310.pyc b/code_construction/__pycache__/code_construction.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..807e2aff99cfaf07d918dca8028c88ec2ae6a92f GIT binary patch literal 17715 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 literal 0 HcmV?d00001 diff --git a/code_construction/code_construction.py b/code_construction/code_construction.py index 1ff10f9..12bace4 100644 --- a/code_construction/code_construction.py +++ b/code_construction/code_construction.py @@ -71,9 +71,7 @@ class CodeConstructor: def __init__(self, method="qc-ldpc-hgp", para_dict=None) -> None: """ - method(str): The method of the code construction, 'canonical', 'qc-ldpc-hgp', 'bivariate-bycicle','rotated-surface','qc-gb'. - - + method(str): The method of the code construction, 'canonical', 'qc-ldpc-hgp', 'bivariate-bycicle','rotated-surface','qc-gb' """ self.method = method self.para_dict = para_dict @@ -139,6 +137,8 @@ def construct(self, parameters): return self.arbitrary_bivariate_bicycle_code(parameters) elif self.method == "gbb": return self.generalised_bivariate_bicycle_code(parameters) + elif self.method == "gb": + return self.generalised_bicycle_code(parameters) elif self.method == "symmetric-qc-gb": return self.symmetric_quasi_cyclic_generalized_bicycle_code(parameters) else: @@ -436,6 +436,40 @@ def ldpc_construction(self, p, q, m, M): # Place H_ij in the corresponding block of H H[i * m : (i + 1) * m, j * m : (j + 1) * m] = H_ij return H + + def generalised_bicycle_code(self, parameters) -> CSSCode: + # calculate factors of x^l - 1: f1, f2, ... fn. each is a number corresponding to a binary string (5=101=x2+1) + # g(x) is a number corresponding to a binary string representing which factors it is made from (6=110=f1*f2) + # a(x) is a multiple of g(x): a(x) = g(x) * q(x). q(x) is represented like fn. to mutate a(x), flip 1 bit in q(x) + # coefficients of a(x) make the first row of A, make square and circulant. same for b(x), same g(x) but * q'(x) + + # parameters: f1, ..., fn, g(x), q(x), q'(x) + l = self.para_dict["l"] + + parameters = parameters.split(',') + gx, qa, qb = parameters[-3:] + fs = parameters[:-3] + + # TODO fix this math stuff + + gx_bin_list = np.array([fs[i] for i in gx]).flatten() + gx_bin = int(''.join(map(str, gx_bin_list)), 2) + + a = 0 + for i, bit in enumerate(qa): + a ^= (gx_bin * bit) << i + a = [int(bit) for bit in bin(a)[2:]] + + b = 0 + for i, bit in enumerate(qb): + b ^= (gx_bin * bit) << i + b = [int(bit) for bit in bin(b)[2:]] + + + A = np.array([[a]]) + B = np.array([[b]]) + + return self.quasi_cyclic_generalized_bicycle_code({"A": A, "B": B}) def bivariate_bycicle_construction(self, parameters) -> CSSCode: """ @@ -574,14 +608,12 @@ def corresponding_matrix_Ba(self, g_size, a): return B_a def g_power(self, g_size, p): - g_p = np.zeros((g_size, g_size), dtype=int) for i in range(g_size): g_p[i][int((i + p) % g_size)] = 1 return g_p def arbitrary_bivariate_bicycle_code(self, parameters): - # a=[a_0,a_1,...,a_{l+g-2}] is a rep of coefficient of A=a_0*I + a_1*x + a_2*x^2 +...+ a_{l-1}*x^{l-1} + a_l*y + a_{l+1}*y^2 +...+ a_{l+g-2}*y^{g-1} l = self.para_dict["l"] g = self.para_dict["g"] @@ -627,6 +659,17 @@ def generalised_bivariate_bicycle_code(self, parameters): if b[idx] == 1: B[i][(i + j) % l][k] = 1 + # untested vectorised optimisation: + # a_base = parameters[: (l * g)].reshape(l, g) + # b_base = parameters[(l * g) :].reshape(l, g) + + # row_idx = np.arange(l)[:, None] + # col_idx = np.arange(l)[None, :] + # shift_matrix = (col_idx - row_idx) % l + + # A = a_base[shift_matrix] + # B = b_base[shift_matrix] + return self.quasi_cyclic_generalized_bicycle_code({"A": A, "B": B}) def symmetric_quasi_cyclic_generalized_bicycle_code(self, A): From fbf2b57d00bc4c57c7365da7f610caaac3b96028 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Wed, 25 Mar 2026 17:30:20 +0000 Subject: [PATCH 09/36] added code to generate new starting gb codes, excluding selection of g which should probably be passed as an argument --- BOplayground.py | 66 +++++++++++++++++++++++++- code_construction/code_construction.py | 60 +++++++++++++---------- 2 files changed, 100 insertions(+), 26 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index 06f66b4..abd9b98 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -6,6 +6,7 @@ import torch from torch.nn.utils import clip_grad_norm_ from torch.distributions.normal import Normal +import sympy as sp from code_construction.code_construction import CodeConstructor from bayesian_optimization.objective_function import ObjectiveFunction @@ -983,17 +984,27 @@ def set_all_seeds(seed: int = 42): class Get_new_points_function: def __init__( - self, method="qc-ldpc-hgp", code_constructor=None, encode="None", density=None + self, + method="qc-ldpc-hgp", + code_constructor=None, + encode="None", + density=None, + desired_k=None, + rho=None, ): self.method = method self.code_constructor = code_constructor self.encode = encode self.density = density self.init = False + self.desired_k = desired_k + self.rho = rho def get_new_points_function(self, number): if self.method == "qc-ldpc-hgp": new_points = self.get_new_points_HGP(number) + elif self.method == "gb": + new_points = self.get_new_gb_vector(number) elif self.method == "bb": new_points = self.get_new_bb_vector(number) elif self.method == "gbb": @@ -1007,6 +1018,59 @@ def get_new_points_HGP(self, number): (number, self.hyperparameters["p"] * self.hyperparameters["q"]), ) + def _get_irreducable_factors(self, l) -> list[int]: + x = sp.Symbol("x") + poly = x**l - 1 + _, factors_counts = sp.factor_list(poly, domain=sp.GF(2)) + + int_factors = [] + for factor, count in factors_counts: + coeffs = sp.Poly(factor, x).all_coeffs() + binary_coeffs = [int(c) % 2 for c in coeffs][::-1] + + # convert binary array to int + factor_int = sum([bit * (1 << i) for i, bit in enumerate(binary_coeffs)]) + + for _ in range(count): + int_factors.append(factor_int) + + return int_factors + + def get_new_gb_vector(self, number): + results = [] + l = self.code_constructor.para_dict["l"] + factors = self._get_irreducable_factors(l) + max_bound = 2 ** (l - (gx_bin.bit_length() - 1)) + # maybe let user pick factors here for g + # TODO decide gx_mask + gx_mask = 0 + gx_bin = 1 + for i in range(gx_mask.bit_length()): + if (gx_mask >> i) & 1: + gx_bin = self.multiply_polynomials_mod_l(gx_bin, fs[i], l) + + while number > 0: + qa = np.random.randint(1, max_bound) + weight_a = self.code_constructor.multiply_polynomials_mod_l( + g, qa, l + ).bit_count() + if weight_a != self.rho: + continue + + weight_b = -1 + while weight_b != self.rho: + qb = np.random.randint(1, max_bound) + weight_b = self.code_constructor.multiply_polynomials_mod_l( + g, qb, l + ).bit_count() + + parameters = [gx_mask, qa, qb] + factors + + results.append(parameters) + number -= 1 + + return np.array(results) + def get_new_bb_vector(self, number): results = [] l = self.code_constructor.para_dict["l"] diff --git a/code_construction/code_construction.py b/code_construction/code_construction.py index 12bace4..c337b2f 100644 --- a/code_construction/code_construction.py +++ b/code_construction/code_construction.py @@ -436,38 +436,48 @@ def ldpc_construction(self, p, q, m, M): # Place H_ij in the corresponding block of H H[i * m : (i + 1) * m, j * m : (j + 1) * m] = H_ij return H - + + def multiply_polynomials_mod_l(self, p1, p2, l) -> int: + res = 0 + for i in range(p2.bit_length()): + if (p2 >> i) & 1: # checks i-th bit is 1 + shifted = p1 << i + overflow = ( + shifted >> l + ) # mod x^l, higher degree polynomials wrap around + not_overflow = shifted & ((1 << l) - 1) + res ^= not_overflow ^ overflow + + return res + def generalised_bicycle_code(self, parameters) -> CSSCode: - # calculate factors of x^l - 1: f1, f2, ... fn. each is a number corresponding to a binary string (5=101=x2+1) - # g(x) is a number corresponding to a binary string representing which factors it is made from (6=110=f1*f2) - # a(x) is a multiple of g(x): a(x) = g(x) * q(x). q(x) is represented like fn. to mutate a(x), flip 1 bit in q(x) - # coefficients of a(x) make the first row of A, make square and circulant. same for b(x), same g(x) but * q'(x) - - # parameters: f1, ..., fn, g(x), q(x), q'(x) + """ + parameters = [gx_mask, qa, qb, f1, ..., fn] + gx_mask (int): binary mask of which irreducable factors of (x^l - 1) make up q(x) + qa (int): binary representation of polynomial q_a(x) + qb (int): binary representation of polynomial q_b(x) + fk (int): binary representation of the irreducable factors of (x^l - 1) + """ l = self.para_dict["l"] - - parameters = parameters.split(',') - gx, qa, qb = parameters[-3:] - fs = parameters[:-3] - - # TODO fix this math stuff - gx_bin_list = np.array([fs[i] for i in gx]).flatten() - gx_bin = int(''.join(map(str, gx_bin_list)), 2) + gx_mask, qa, qb = parameters[:3] + fs = parameters[3:] - a = 0 - for i, bit in enumerate(qa): - a ^= (gx_bin * bit) << i - a = [int(bit) for bit in bin(a)[2:]] + # compute g(x) polynomial + gx_bin = 1 + for i in range(gx_mask.bit_length()): + if (gx_mask >> i) & 1: + gx_bin = self.multiply_polynomials_mod_l(gx_bin, fs[i], l) - b = 0 - for i, bit in enumerate(qb): - b ^= (gx_bin * bit) << i - b = [int(bit) for bit in bin(b)[2:]] + # calculate a(x) = g(x) * q_a(x), b(x) = g(x) * q_b(x) + a_int = self.multiply_polynomials_mod_l(gx_bin, qa, l) + b_int = self.multiply_polynomials_mod_l(gx_bin, qb, l) + a_array = [int(bit) for bit in bin(a_int)[2:].zfill(l)[::-1]] + b_array = [int(bit) for bit in bin(b_int)[2:].zfill(l)[::-1]] - A = np.array([[a]]) - B = np.array([[b]]) + A = np.array([[a_array]]) + B = np.array([[b_array]]) return self.quasi_cyclic_generalized_bicycle_code({"A": A, "B": B}) From 0a5c27a2c9531c3b0e91e0e4b2cef9bbe78d0e74 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Thu, 26 Mar 2026 17:07:59 +0000 Subject: [PATCH 10/36] modifed hill climbing to find different qa and qb factors for a(x) and b(x) --- BOplayground.py | 74 ++++++++++++++++++++++---- code_construction/code_construction.py | 22 +++++--- read_results.ipynb | 4 +- 3 files changed, 80 insertions(+), 20 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index abd9b98..bf5b428 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -42,8 +42,12 @@ def __init__( validator # 例如 lambda z: code_constructor.construct(z).k != 0 ) match method: + case "gb": + self.mutate = self.swap_factors case "gbb": - self.mutate = self.swap_neighbours + self.mutate = ( + self.swap_neighbours + ) # or self.swap_neighbours_within_matrices case "bb": self.mutate = self.flip_neighbours case _: @@ -76,6 +80,58 @@ def swap_neighbours(self, x: torch.Tensor) -> torch.Tensor: neigh_list.append(n) return torch.stack(neigh_list, dim=0) + def swap_factors(self, x: torch.Tensor) -> torch.Tensor: + l = gnp.code_constructor.para_dict["l"] + target_row_weight = gnp.code_constructor.rho + + x = x.detach() + + gx_mask = int(x[0].item()) + qa = int(x[1].item()) + qb = int(x[2].item()) + fs = [int(f.item()) for f in x[3:]] + + gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, l) + + max_bound = 2 ** (l - (gx_bin.bit_length() - 1)) + + neigh_list = [] + for i in range(max_bound.bit_length()): + for j in range(i, max_bound.bit_length()): + if i == j: + new_qa = qa ^ (1 << i) # flips one factor of qa + new_qb = qb ^ (1 << i) + else: + new_qa = qa ^ (1 << i) ^ (1 << j) # flips two factors of qa + new_qb = qb ^ (1 << i) ^ (1 << j) + + a_weight = CodeConstructor.multiply_polynomials_mod_l( + gx_bin, new_qa, l + ).bit_count() + + b_weight = CodeConstructor.multiply_polynomials_mod_l( + gx_bin, new_qb, l + ).bit_count() + + if ( + a_weight == target_row_weight + ): # checks if new a(x) preserves LDPC density + n1 = x.clone() + n1[1] = new_qa + neigh_list.append(n1) + + if ( + b_weight == target_row_weight + ): # checks if new b(x) preserves LDPC density + n2 = x.clone() + n2[2] = new_qb + neigh_list.append(n2) + + if not neigh_list: + return torch.empty((0, len(x)), dtype=x.dtype, device=x.device) + + return torch.stack(neigh_list, dim=0) + # only swaps within A and B so that both matrices always have the same number of 1s def swap_neighbours_within_matrices(self, x: torch.Tensor) -> torch.Tensor: x = x.detach() @@ -1040,19 +1096,17 @@ def get_new_gb_vector(self, number): results = [] l = self.code_constructor.para_dict["l"] factors = self._get_irreducable_factors(l) - max_bound = 2 ** (l - (gx_bin.bit_length() - 1)) # maybe let user pick factors here for g # TODO decide gx_mask gx_mask = 0 - gx_bin = 1 - for i in range(gx_mask.bit_length()): - if (gx_mask >> i) & 1: - gx_bin = self.multiply_polynomials_mod_l(gx_bin, fs[i], l) + gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, factors, l) + + max_bound = 2 ** (l - (gx_bin.bit_length() - 1)) while number > 0: qa = np.random.randint(1, max_bound) - weight_a = self.code_constructor.multiply_polynomials_mod_l( - g, qa, l + weight_a = CodeConstructor.multiply_polynomials_mod_l( + gx_bin, qa, l ).bit_count() if weight_a != self.rho: continue @@ -1060,8 +1114,8 @@ def get_new_gb_vector(self, number): weight_b = -1 while weight_b != self.rho: qb = np.random.randint(1, max_bound) - weight_b = self.code_constructor.multiply_polynomials_mod_l( - g, qb, l + weight_b = CodeConstructor.multiply_polynomials_mod_l( + gx_bin, qb, l ).bit_count() parameters = [gx_mask, qa, qb] + factors diff --git a/code_construction/code_construction.py b/code_construction/code_construction.py index c337b2f..d25158c 100644 --- a/code_construction/code_construction.py +++ b/code_construction/code_construction.py @@ -437,7 +437,8 @@ def ldpc_construction(self, p, q, m, M): H[i * m : (i + 1) * m, j * m : (j + 1) * m] = H_ij return H - def multiply_polynomials_mod_l(self, p1, p2, l) -> int: + @staticmethod + def multiply_polynomials_mod_l(p1, p2, l) -> int: res = 0 for i in range(p2.bit_length()): if (p2 >> i) & 1: # checks i-th bit is 1 @@ -450,6 +451,16 @@ def multiply_polynomials_mod_l(self, p1, p2, l) -> int: return res + @staticmethod + def gx_mask_to_bin(gx_mask, fs, l) -> int: + gx_bin = 1 + + for i in range(gx_mask.bit_length()): + if (gx_mask >> i) & 1: + gx_bin = CodeConstructor.multiply_polynomials_mod_l(gx_bin, fs[i], l) + + return gx_bin + def generalised_bicycle_code(self, parameters) -> CSSCode: """ parameters = [gx_mask, qa, qb, f1, ..., fn] @@ -464,14 +475,11 @@ def generalised_bicycle_code(self, parameters) -> CSSCode: fs = parameters[3:] # compute g(x) polynomial - gx_bin = 1 - for i in range(gx_mask.bit_length()): - if (gx_mask >> i) & 1: - gx_bin = self.multiply_polynomials_mod_l(gx_bin, fs[i], l) + gx_bin = self.gx_mask_to_bin(gx_mask, fs, l) # calculate a(x) = g(x) * q_a(x), b(x) = g(x) * q_b(x) - a_int = self.multiply_polynomials_mod_l(gx_bin, qa, l) - b_int = self.multiply_polynomials_mod_l(gx_bin, qb, l) + a_int = CodeConstructor.multiply_polynomials_mod_l(gx_bin, qa, l) + b_int = CodeConstructor.multiply_polynomials_mod_l(gx_bin, qb, l) a_array = [int(bit) for bit in bin(a_int)[2:].zfill(l)[::-1]] b_array = [int(bit) for bit in bin(b_int)[2:].zfill(l)[::-1]] diff --git a/read_results.ipynb b/read_results.ipynb index 8f37962..bd4f28b 100644 --- a/read_results.ipynb +++ b/read_results.ipynb @@ -20,7 +20,6 @@ "metadata": {}, "outputs": [], "source": [ - "\n", "print(f\"Loading results from: {file_path}\\n\")\n", "\n", "with open(file_path, \"rb\") as f:\n", @@ -30,7 +29,7 @@ "best_y = results[\"best_y\"]\n", "history = results[\"evaluation_history\"]\n", "\n", - "print(f\"Best Score (y): {best_y}\") \n", + "print(f\"Best Score (y): {best_y}\")\n", "print(f\"Binary Vector:\\n{best_x}\\n\")" ] }, @@ -41,7 +40,6 @@ "metadata": {}, "outputs": [], "source": [ - "\n", "if len(history) > 10:\n", " print(f\"First 5 scores: {[round(num, 4) for num in history[:5]]}\")\n", " print(f\"Last 5 scores: {[round(num, 4) for num in history[-5:]]}\")\n", From d2ddb2c54d5d3c81427962ea1a0ee801268a5818 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Wed, 1 Apr 2026 19:25:02 +0100 Subject: [PATCH 11/36] bug fixes for gb, modified main loop to support additional gb parameters, set up a toy gb experiment --- BOplayground.py | 176 ++++++++++++------ .../code_construction.cpython-310.pyc | Bin 17715 -> 0 bytes code_construction/code_construction.py | 12 +- 3 files changed, 128 insertions(+), 60 deletions(-) delete mode 100644 code_construction/__pycache__/code_construction.cpython-310.pyc diff --git a/BOplayground.py b/BOplayground.py index bf5b428..f9859ec 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -6,7 +6,7 @@ import torch from torch.nn.utils import clip_grad_norm_ from torch.distributions.normal import Normal -import sympy as sp +import sympy from code_construction.code_construction import CodeConstructor from bayesian_optimization.objective_function import ObjectiveFunction @@ -32,6 +32,8 @@ def __init__( device="cuda", validator=None, method="bb", + l=None, + target_row_weight=None, ): self.next_points_num = int(next_points_num) @@ -41,6 +43,8 @@ def __init__( self.validator = ( validator # 例如 lambda z: code_constructor.construct(z).k != 0 ) + self.l = l + self.target_row_weight = target_row_weight match method: case "gb": self.mutate = self.swap_factors @@ -81,9 +85,6 @@ def swap_neighbours(self, x: torch.Tensor) -> torch.Tensor: return torch.stack(neigh_list, dim=0) def swap_factors(self, x: torch.Tensor) -> torch.Tensor: - l = gnp.code_constructor.para_dict["l"] - target_row_weight = gnp.code_constructor.rho - x = x.detach() gx_mask = int(x[0].item()) @@ -91,9 +92,9 @@ def swap_factors(self, x: torch.Tensor) -> torch.Tensor: qb = int(x[2].item()) fs = [int(f.item()) for f in x[3:]] - gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, l) + gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, self.l) - max_bound = 2 ** (l - (gx_bin.bit_length() - 1)) + max_bound = 2 ** (self.l - (gx_bin.bit_length() - 1)) neigh_list = [] for i in range(max_bound.bit_length()): @@ -106,22 +107,22 @@ def swap_factors(self, x: torch.Tensor) -> torch.Tensor: new_qb = qb ^ (1 << i) ^ (1 << j) a_weight = CodeConstructor.multiply_polynomials_mod_l( - gx_bin, new_qa, l + gx_bin, new_qa, self.l ).bit_count() b_weight = CodeConstructor.multiply_polynomials_mod_l( - gx_bin, new_qb, l + gx_bin, new_qb, self.l ).bit_count() if ( - a_weight == target_row_weight + a_weight == self.target_row_weight ): # checks if new a(x) preserves LDPC density n1 = x.clone() n1[1] = new_qa neigh_list.append(n1) if ( - b_weight == target_row_weight + b_weight == self.target_row_weight ): # checks if new b(x) preserves LDPC density n2 = x.clone() n2[2] = new_qb @@ -1046,7 +1047,7 @@ def __init__( encode="None", density=None, desired_k=None, - rho=None, + gx_mask=None, ): self.method = method self.code_constructor = code_constructor @@ -1054,7 +1055,8 @@ def __init__( self.density = density self.init = False self.desired_k = desired_k - self.rho = rho + self.gx_mask = gx_mask + self.factors = None def get_new_points_function(self, number): if self.method == "qc-ldpc-hgp": @@ -1074,14 +1076,14 @@ def get_new_points_HGP(self, number): (number, self.hyperparameters["p"] * self.hyperparameters["q"]), ) - def _get_irreducable_factors(self, l) -> list[int]: - x = sp.Symbol("x") + def _get_irreducible_factors(self, l) -> list[int]: + x = sympy.Symbol("x") poly = x**l - 1 - _, factors_counts = sp.factor_list(poly, domain=sp.GF(2)) + _, factors_counts = sympy.factor_list(poly, domain=sympy.GF(2)) int_factors = [] for factor, count in factors_counts: - coeffs = sp.Poly(factor, x).all_coeffs() + coeffs = sympy.Poly(factor, x).all_coeffs() binary_coeffs = [int(c) % 2 for c in coeffs][::-1] # convert binary array to int @@ -1092,36 +1094,84 @@ def _get_irreducable_factors(self, l) -> list[int]: return int_factors + def _get_possible_gx_bitmasks(self, factors): + if self.desired_k is None: + raise ValueError( + "A desired value of k is required for GB codes if g(x) bitmask not provided." + ) + + target_degree = self.desired_k // 2 + no_factors = len(factors) + + degrees = [f.bit_length() - 1 for f in factors] + valid_bitmasks = [] + + for i in range(1, 1 << no_factors): # iterate through all possible bitmasks + degree = 0 + for j in range(no_factors): + if (i >> j) & 1: + degree += degrees[j] + if degree == target_degree: + valid_bitmasks.append(i) + + return valid_bitmasks + + def set_gx_mask(self): + l = self.code_constructor.para_dict["l"] + self.factors = self._get_irreducible_factors(l) + if self.gx_mask is not None: + if not (self.gx_mask > 0 and self.gx_mask < (1 << len(self.factors))): + raise ValueError( + f"Provided g(x) bitmask {self.gx_mask} is not valid: must be int in range [1, {(1 << len(self.factors)) - 1}] for l={l}" + ) + else: + possible_gx_masks = self._get_possible_gx_bitmasks(self.factors) + self.gx_mask = random.choice(possible_gx_masks) + print(f"Randomly selected g(x) bitmask = {self.gx_mask}") + def get_new_gb_vector(self, number): results = [] l = self.code_constructor.para_dict["l"] - factors = self._get_irreducable_factors(l) - # maybe let user pick factors here for g - # TODO decide gx_mask - gx_mask = 0 - gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, factors, l) + + gx_bin = CodeConstructor.gx_mask_to_bin(self.gx_mask, self.factors, l) max_bound = 2 ** (l - (gx_bin.bit_length() - 1)) while number > 0: - qa = np.random.randint(1, max_bound) - weight_a = CodeConstructor.multiply_polynomials_mod_l( - gx_bin, qa, l - ).bit_count() - if weight_a != self.rho: - continue + batch_guesses = np.random.randint(1, max_bound, size=10_000) + qa_found = False - weight_b = -1 - while weight_b != self.rho: - qb = np.random.randint(1, max_bound) - weight_b = CodeConstructor.multiply_polynomials_mod_l( - gx_bin, qb, l + for qa in batch_guesses: + qa = qa.item() + + # qa = np.random.randint(1, max_bound) + weight_a = CodeConstructor.multiply_polynomials_mod_l( + gx_bin, qa, l ).bit_count() + # if weight_a != self.density: + # continue + if weight_a == self.density: + qa_found = True + break - parameters = [gx_mask, qa, qb] + factors + if qa_found: + qb_found = False + while not qb_found: + batch_guesses = np.random.randint(1, max_bound, size=10_000) + + for qb in batch_guesses: + qb = qb.item() + weight_b = CodeConstructor.multiply_polynomials_mod_l( + gx_bin, qb, l + ).bit_count() + if weight_b == self.density: + qb_found = True + break - results.append(parameters) - number -= 1 + parameters = [self.gx_mask, qa, qb] + self.factors + + results.append(parameters) + number -= 1 return np.array(results) @@ -1186,32 +1236,33 @@ def get_new_gbb_vector(self, number): # default values code_class = "bb" - density = 3 + density = 4 + seed = 42 + dataset_index = 0 + lambda_ = 1 + desired_k = None + gx_mask = 1 args = sys.argv[1:] if args[0].isalpha(): code_class = args.pop(0) - if len(args) == 3: - seed = int(args[1]) - dataset_index = int(args[2]) - lambda_ = 1 - elif len(args) == 4: - seed = int(args[1]) - dataset_index = int(args[2]) - lambda_ = float(args[3]) - elif len(args) == 5: + if len(args) >= 3: seed = int(args[1]) dataset_index = int(args[2]) + if len(args) >= 4: lambda_ = float(args[3]) + if len(args) >= 5: density = int(args[4]) - else: - seed = 42 - dataset_index = 0 - lambda_ = 1 + if len(args) >= 6: + if args[5][:2] == "k=": + desired_k = int(args[5][2:]) + elif args[5][:2] == "g=": + gx_mask = int(args[5][2:]) set_all_seeds(seed) - l = 12 + # l = 12 + l = 7 g = 6 # g here is m in Bravyi et al's paper print( f"(l,g)=({l},{g}), dataset_index = {dataset_index}, seed={seed}, lambda = {lambda_}" @@ -1228,9 +1279,14 @@ def get_new_gbb_vector(self, number): obj_func = Obj_Func.forward pl_to_obj = Obj_Func.pl_to_obj_with_std # method of sampling new points - gnp = Get_new_points_function( - method=code_class, code_constructor=code_constructor, density=density - ).get_new_points_function + gnp_obj = Get_new_points_function( + method=code_class, + code_constructor=code_constructor, + density=density, + desired_k=desired_k, + gx_mask=gx_mask, + ) + gnp = gnp_obj.get_new_points_function # initial points: # init_num = 20 # X_init = gnp(init_num) @@ -1240,7 +1296,10 @@ def get_new_gbb_vector(self, number): # y,pl = obj_func(x) # y_init.append(y) # pl_init.append(pl) - if code_class == "gbb": + if code_class == "gb": + gnp_obj.set_gx_mask() + init_data_file = f"./data/BO_initial_points/GB_BO_initial_points_{dataset_index}_{lambda_}_{density}_{l}_{gnp_obj.gx_mask}.pkl" + elif code_class == "gbb": init_data_file = f"./data/BO_initial_points/GBB_BO_initial_points_{dataset_index}_{lambda_}_{density}_{l}_{g}.pkl" else: # bb if l == 6 and g == 3: @@ -1348,6 +1407,8 @@ def code_validator(candidate_np): device=DEVICE, validator=code_validator, method=code_class, + l=l, + target_row_weight=density, ) # assemble BO @@ -1373,9 +1434,8 @@ def code_validator(candidate_np): y_init_list = [i.item() for i in y_init] flat = y_init_list + flat - with open( - f"./data/BO_results/{code_class}_BO_{l}_{g}_{dataset_index}_{seed}_{lambda_}.pkl", - "wb", - ) as f: + results_file = init_data_file.replace("BO_initial_points", "BO_results") + + with open(results_file, "wb") as f: results = {"best_x": best_x, "best_y": best_y, "evaluation_history": flat} pickle.dump(results, f) diff --git a/code_construction/__pycache__/code_construction.cpython-310.pyc b/code_construction/__pycache__/code_construction.cpython-310.pyc deleted file mode 100644 index 807e2aff99cfaf07d918dca8028c88ec2ae6a92f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17715 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 CSSCode: """ l = self.para_dict["l"] - gx_mask, qa, qb = parameters[:3] - fs = parameters[3:] + gx_mask, qa, qb = [int(x) for x in parameters[:3]] + fs = [int(x) for x in parameters[3:]] # compute g(x) polynomial gx_bin = self.gx_mask_to_bin(gx_mask, fs, l) From 06dcf360084e71cca8b3015db695353028305cd2 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Wed, 15 Apr 2026 15:13:03 +0100 Subject: [PATCH 12/36] added some tests and bug fixes. tests work, but BO stuff (untested as it is existing code) does not as int representation needs to be replaced with binary vecs. --- BOplayground.py | 6 +- code_construction/code_construction.py | 35 ++- tests/test_gb_codes.ipynb | 368 +++++++++++++++++++++++++ 3 files changed, 391 insertions(+), 18 deletions(-) create mode 100644 tests/test_gb_codes.ipynb diff --git a/BOplayground.py b/BOplayground.py index f9859ec..3286ab8 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -94,11 +94,11 @@ def swap_factors(self, x: torch.Tensor) -> torch.Tensor: gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, self.l) - max_bound = 2 ** (self.l - (gx_bin.bit_length() - 1)) + max_bound = self.l - (gx_bin.bit_length() - 1) neigh_list = [] - for i in range(max_bound.bit_length()): - for j in range(i, max_bound.bit_length()): + for i in range(max_bound): + for j in range(i, max_bound): if i == j: new_qa = qa ^ (1 << i) # flips one factor of qa new_qb = qb ^ (1 << i) diff --git a/code_construction/code_construction.py b/code_construction/code_construction.py index cc94095..d539e5b 100644 --- a/code_construction/code_construction.py +++ b/code_construction/code_construction.py @@ -468,6 +468,19 @@ def gx_mask_to_bin(gx_mask, fs, l) -> int: gx_bin = CodeConstructor.multiply_polynomials_mod_l(gx_bin, fs[i], l) return gx_bin + + def _build_circulant_matrix(self, row) -> np.ndarray: + length = len(row) + col_idx = np.arange(length)[None, :] + row_idx = np.arange(length)[:, None] + + shift_matrix = (col_idx - row_idx) % length + return row[shift_matrix] + + def _build_bicycle_css_code(self, A, B) -> CSSCode: + HX = np.hstack((A, B)) + HZ = np.hstack((B.T, A.T)) + return CSSCode(HX, HZ) def generalised_bicycle_code(self, parameters) -> CSSCode: """ @@ -489,13 +502,13 @@ def generalised_bicycle_code(self, parameters) -> CSSCode: a_int = CodeConstructor.multiply_polynomials_mod_l(gx_bin, qa, l) b_int = CodeConstructor.multiply_polynomials_mod_l(gx_bin, qb, l) - a_array = [int(bit) for bit in bin(a_int)[2:].zfill(l)[::-1]] - b_array = [int(bit) for bit in bin(b_int)[2:].zfill(l)[::-1]] + a_array = np.array([int(bit) for bit in bin(a_int)[2:].zfill(l)[::-1]]) + b_array = np.array([int(bit) for bit in bin(b_int)[2:].zfill(l)[::-1]]) - A = np.array([[a_array]]) - B = np.array([[b_array]]) + A = self._build_circulant_matrix(a_array) + B = self._build_circulant_matrix(b_array) - return self.quasi_cyclic_generalized_bicycle_code({"A": A, "B": B}) + return self._build_bicycle_css_code(A, B) def bivariate_bycicle_construction(self, parameters) -> CSSCode: """ @@ -536,16 +549,8 @@ def bivariate_bycicle_construction(self, parameters) -> CSSCode: B += np.linalg.matrix_power(y, (B_p[i][1] % m)) A = A % 2 B = B % 2 - # for i in A: - # for j in i: - # print(f'{int(j)} ',end='') - # print() - # print(f'A:\n{A}\n,B:\n{B}') - HX = np.hstack((A, B)) - HZ = np.hstack((B.T, A.T)) - # print(f'n:{2*l*m}\n,rank(A):\n{np.linalg.matrix_rank(A)},\n\nrank(B):\n{np.linalg.matrix_rank(B)}') - css = CSSCode(HX, HZ) - return css + + return self._build_bicycle_css_code(A, B) def pg_ldpc_hgp_construction(self, M): H1 = self.pg_ldpc_construction( diff --git a/tests/test_gb_codes.ipynb b/tests/test_gb_codes.ipynb new file mode 100644 index 0000000..bde2405 --- /dev/null +++ b/tests/test_gb_codes.ipynb @@ -0,0 +1,368 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "0", + "metadata": {}, + "outputs": [], + "source": [ + "def int_to_poly(p: int):\n", + " poly = \"\"\n", + " for i in range(p.bit_length()):\n", + " bit = (p >> i) & 1\n", + " if bit:\n", + " if i == 0:\n", + " add = \"1\"\n", + " elif i == 1:\n", + " add = \"x\"\n", + " else:\n", + " add = f\"x^{i}\"\n", + " \n", + " if poly == \"\":\n", + " poly = add\n", + " else:\n", + " poly += f\" + {add}\"\n", + "\n", + " return poly\n", + "\n", + "\n", + "print(int_to_poly(19))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "from pathlib import Path\n", + "\n", + "path_root = Path.cwd().parent\n", + "\n", + "if str(path_root) not in sys.path:\n", + " sys.path.append(str(path_root))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "# TEST: does multiply_polynomials_mod_l correctly multiply polynomials with coefficients mod 2 and exponents mod l\n", + "from code_construction.code_construction import CodeConstructor, CSSCode\n", + "\n", + "p1 = 417 # 110100001 -> 1 + x^5 + x^7 + x^8\n", + "p2 = 9 # 1001 -> 1 + x^3 \n", + "\n", + "l = 9\n", + "\n", + "# p1 * p2 = 1 + x^5 + x^7 + x^8 + x^3 + x^8 + x^10 + x^11 = 1 + x^5 + x^7 + x^8 + x^3 + x^8 + x + x^2 = 1 + x + x^2 + x^3 + x^5 + x^7\n", + "\n", + "p1p2 = CodeConstructor.multiply_polynomials_mod_l(p1, p2, l)\n", + "print(int_to_poly(p1))\n", + "print(int_to_poly(p2))\n", + "print(int_to_poly(p1p2))\n", + "\n", + "assert int_to_poly(p1p2) == \"1 + x + x^2 + x^3 + x^5 + x^7\"\n", + "\n", + "print(\"Test Passed!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "# TEST: does gx_mask_to_bin correctly generate the g(x) polynomial in int form given a list of factors and a bitmask\n", + "fs = [\n", + " 5, # 101 -> 1+x^2\n", + " 49, # 110001 -> 1+x^4+x^5\n", + " 17, # 10001 -> 1+x^4\n", + "]\n", + "\n", + "\n", + "gx_mask = 6 # 110 -> (1+x^4+x^5)(1+x^4) = 1 + x^4 + x^5 + x^4 + x^8 + x^9 = 1 + x^5 + x^8 + 1 = x^5 + x^8\n", + "\n", + "gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, l)\n", + "\n", + "for f in fs:\n", + " print(int_to_poly(f))\n", + "\n", + "print(int_to_poly(gx_bin))\n", + "\n", + "assert int_to_poly(gx_bin) == \"x^5 + x^8\"\n", + "\n", + "print(\"Test Passed!\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# TEST: are randomly generated GB codes valid\n", + "from BOplayground import Get_new_points_function\n", + "\n", + "# this is the same as multiply_polynomials_mod_l but without the mod l\n", + "def binary_poly_mul(p1, p2):\n", + " res = 0\n", + " for i in range(p2.bit_length()):\n", + " if (p2 >> i) & 1:\n", + " res ^= (p1 << i)\n", + " return res\n", + "\n", + "\n", + "para_dict = {\"l\": l}\n", + "code_class = 'gb'\n", + "target_weight = 3\n", + "desired_k = 12\n", + "\n", + "cc = CodeConstructor(\n", + " method=code_class,\n", + " para_dict=para_dict\n", + ")\n", + "\n", + "\n", + "gnp = Get_new_points_function(\n", + " method=code_class,\n", + " code_constructor=cc,\n", + " density=target_weight,\n", + " desired_k=desired_k\n", + ")\n", + "\n", + "gnp.set_gx_mask()\n", + "\n", + "\n", + "x_exp_l_minus_1 = 1 + (1 << l) # x^l - 1 = x^l + 1 in binary ring\n", + "\n", + "new_points = gnp.get_new_points_function(5)\n", + "\n", + "for point in new_points:\n", + " gx_mask, qa, qb = [int(x) for x in point[:3]]\n", + " fs = [int(x) for x in point[3:]]\n", + " prod_fs = 1\n", + " for f in fs:\n", + " prod_fs = binary_poly_mul(prod_fs, f)\n", + "\n", + "\n", + " assert prod_fs == x_exp_l_minus_1\n", + "\n", + " gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, l)\n", + " assert CodeConstructor.multiply_polynomials_mod_l(gx_bin, qa, l).bit_count() == target_weight # LHS: a(x)\n", + " assert CodeConstructor.multiply_polynomials_mod_l(gx_bin, qb, l).bit_count() == target_weight # LHD: b(x)\n", + "\n", + "print(\"Test Passed!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "small_l = 3\n", + "print(\"l=3\")\n", + "\n", + "# factors of x^3 - 1 in the ring should be (x + 1)(x^2 + x + 1)\n", + "factors = gnp._get_irreducible_factors(small_l)\n", + "for f in factors:\n", + " print(f\"{int_to_poly(f)} ({f})\")\n", + "\n", + "print(\"\\n\")\n", + "print(\"l=9\")\n", + "\n", + "# factors of x^3 - 1 in the ring should be (x + 1)(x^2 + x + 1)\n", + "factors = gnp._get_irreducible_factors(l)\n", + "for f in factors:\n", + " print(f\"{int_to_poly(f)} ({f})\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "from sympy import symbols, factor, expand, GF\n", + "\n", + "def factor_poly(poly):\n", + " factors = factor(poly, domain=GF(2))\n", + " return factors\n", + "\n", + "x = symbols('x')\n", + "print(factor_poly(1+x+x**5))\n", + "\n", + "print(expand((1 + x**2 + x**3)*(1 + x), modulus=2))\n", + "print(expand((1 + x + x**2)*(1 + x), modulus=2))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "# TEST: is the CSS code generated by CodeConstructor correct for a given GB internal representation\n", + "import numpy as np\n", + "a = 23 # 000010111 -> 1 + x + x^2 + x^4\n", + "b = 9 # 000001001 -> 1 + x^3\n", + "\n", + "qa = 13 # 1101 -> 1 + x^2 + x^3\n", + "qb = 7 # 111 -> 1 + x + x^2\n", + "gx_mask = 1 # 001, gx_bin=3\n", + "fs = [3, 7, 73] # from l=9 code above\n", + "gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, l)\n", + "\n", + "assert CodeConstructor.multiply_polynomials_mod_l(gx_bin, qa, l) == a\n", + "assert CodeConstructor.multiply_polynomials_mod_l(gx_bin, qb, l) == b\n", + "\n", + "x = [gx_mask, qa, qb] + fs\n", + "\n", + "a_lsb_first = [0,0,0,0,1,0,1,1,1][::-1] # bit order is flipped for A&B matrices (least significant bit on the left)\n", + "b_lsb_first = [0,0,0,0,0,1,0,0,1][::-1]\n", + "\n", + "expected_A = np.array([\n", + " a_lsb_first,\n", + " np.roll(a_lsb_first, 1),\n", + " np.roll(a_lsb_first, 2),\n", + " np.roll(a_lsb_first, 3),\n", + " np.roll(a_lsb_first, 4),\n", + " np.roll(a_lsb_first, 5),\n", + " np.roll(a_lsb_first, 6),\n", + " np.roll(a_lsb_first, 7),\n", + " np.roll(a_lsb_first, 8),\n", + "])\n", + "\n", + "expected_B = np.array([\n", + " b_lsb_first,\n", + " np.roll(b_lsb_first, 1),\n", + " np.roll(b_lsb_first, 2),\n", + " np.roll(b_lsb_first, 3),\n", + " np.roll(b_lsb_first, 4),\n", + " np.roll(b_lsb_first, 5),\n", + " np.roll(b_lsb_first, 6),\n", + " np.roll(b_lsb_first, 7),\n", + " np.roll(b_lsb_first, 8),\n", + "])\n", + "\n", + "expected_hx = np.hstack((expected_A, expected_B))\n", + "\n", + "expected_hz = np.hstack((expected_B.T, expected_A.T))\n", + "\n", + "css = cc.construct(x)\n", + "\n", + "assert isinstance(css, CSSCode)\n", + "assert (((css.hx @ css.hz.T) % 2) == 0).all() # CSS parity check constraint\n", + "\n", + "assert np.array_equal(css.hx, expected_hx)\n", + "assert np.array_equal(css.hz, expected_hz)\n", + "\n", + "print(\"Test Passed!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "# TEST: are generated neighbours of a GB code valid, do they have the same g(x) and have one q the same, and one different\n", + "from BOplayground import HillClimbing\n", + "import torch\n", + "\n", + "# gx_mask = None\n", + "# qa = None\n", + "# qb = None\n", + "\n", + "# l = None\n", + "\n", + "# fs = []\n", + "qa = 3 # 11 -> 1 + x\n", + "qb = 5 # 111 -> 1 + x + x^2\n", + "gx_mask = 3 # 001\n", + "\n", + "fs = [3, 7, 73] # from l=9 code above\n", + "gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, l)\n", + "\n", + "\n", + "x = [gx_mask, qa, qb] + fs\n", + "\n", + "# CodeConstructor.multiply_polynomials_mod_l(gx_bin, n[1], l).bit_count()\n", + "\n", + "# gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, l)\n", + "target_weight2 = 4\n", + "\n", + "gnp2 = Get_new_points_function(\n", + " method=code_class,\n", + " code_constructor=cc,\n", + " density=target_weight2,\n", + " desired_k=desired_k\n", + ")\n", + "\n", + "gnp2.set_gx_mask()\n", + "\n", + "hc = HillClimbing(\n", + " next_points_num=1,\n", + " gnp=gnp2.get_new_points_function,\n", + " acquisition=None,\n", + " l=l,\n", + " target_row_weight=target_weight2\n", + ")\n", + "\n", + "x_tensor = torch.tensor(x, dtype=torch.float32)\n", + "\n", + "x_neighbours = hc.swap_factors(x_tensor)\n", + "assert len(x_neighbours) > 0\n", + "print(len(x_neighbours))\n", + "\n", + "for n in x_neighbours:\n", + " n = n.to(torch.long).tolist()\n", + " assert n[0] == gx_mask\n", + " assert (n[1] == qa) or (n[2] == qb)\n", + " assert np.array_equal(n[3:], fs)\n", + "\n", + " assert CodeConstructor.multiply_polynomials_mod_l(gx_bin, n[1], l).bit_count() == target_weight2\n", + " assert CodeConstructor.multiply_polynomials_mod_l(gx_bin, n[2], l).bit_count() == target_weight2\n", + " assert n != x\n", + "\n", + " cc.construct(n) # CSSCode has built in checks to make sure the code is valid\n", + "\n", + "print(\"Test Passed!\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qec", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.19" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From fba25f1111ba708a81414ee5faf4e30391c6a320 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Thu, 16 Apr 2026 11:27:28 +0100 Subject: [PATCH 13/36] small refactor plus removed clamp and promoted floats that deal with internal code rep from single to double precision (required for large values of l as using ints rather than binary - these are coverted to single precision before the ML stuff by the encoder and embdedder so should not affect performance) --- BOplayground.py | 18 ++++++++---------- bayesian_optimization/bo.py | 8 ++++---- .../code_construction.cpython-311.pyc | Bin 35946 -> 0 bytes 3 files changed, 12 insertions(+), 14 deletions(-) delete mode 100644 code_construction/__pycache__/code_construction.cpython-311.pyc diff --git a/BOplayground.py b/BOplayground.py index 3286ab8..c1858c2 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -163,10 +163,8 @@ def swap_neighbours_within_matrices(self, x: torch.Tensor) -> torch.Tensor: @torch.no_grad() def __call__(self, gp) -> torch.Tensor: - - # 起点:np -> torch.float32 on device X0_np = self.gnp(self.next_points_num) # [n, d], 0/1 - X0 = torch.tensor(X0_np, dtype=torch.float32, device=self.device) + X0 = torch.tensor(X0_np, dtype=torch.float64, device=self.device) best_list = [] for x in X0: # x: [d] @@ -188,7 +186,7 @@ def __call__(self, gp) -> torch.Tensor: best_list.append(best_neighbor) - cand = torch.stack(best_list, dim=0) # [n, d], float32, 0/1 + cand = torch.stack(best_list, dim=0) # [n, d], float64, 0/1 if self.validator is not None: cand_np = cand.detach().cpu().numpy().astype(np.int64) @@ -200,9 +198,9 @@ def __call__(self, gp) -> torch.Tensor: cand_np[i] = self.gnp(1) if tries > 1000: break - cand = torch.tensor(cand_np, dtype=torch.float32, device=self.device) + cand = torch.tensor(cand_np, dtype=torch.float64, device=self.device) - return cand # [n, d], float32(0/1), on device + return cand # [n, d], float64(0/1), on device class EIAcquisitionFunction: @@ -322,10 +320,10 @@ def __call__(self, X, gp: torch.nn.Module): # Ensure X is a proper tensor on the correct device if not torch.is_tensor(X): - X = torch.tensor(X, dtype=torch.float32) + X = torch.tensor(X, dtype=torch.float64) if X.ndim == 1: X = X.unsqueeze(0) - X = X.to(self.device, dtype=torch.float32) + X = X.to(self.device, dtype=torch.float64) # Evaluation mode for GP gp.eval() @@ -1339,8 +1337,8 @@ def get_new_gbb_vector(self, number): y_init = data["y"] pl_init = data["pl"] - X_init = torch.tensor(X_init, dtype=torch.float32) - X_init.to(DEVICE) + X_init = torch.tensor(X_init, dtype=torch.float64) + X_init.to(DEVICE) # think this line should be X_init = X_init.to(DEVICE) y_init = torch.tensor(y_init, dtype=torch.float32) y_init.to(DEVICE) pl_init = torch.tensor(pl_init, dtype=torch.float32) diff --git a/bayesian_optimization/bo.py b/bayesian_optimization/bo.py index 7b02b99..0c1622b 100644 --- a/bayesian_optimization/bo.py +++ b/bayesian_optimization/bo.py @@ -102,7 +102,7 @@ def __init__( self.suggest_next = suggest_next self.device = device - self.X = initial_X.to(self.device, dtype=torch.float32) + self.X = initial_X.to(self.device, dtype=torch.float64) self.pl = initial_pl.to( self.device, dtype=torch.float32 ) # probability space labels @@ -151,7 +151,7 @@ def _latent_posterior(self, X_tensor: torch.Tensor): self.gp.eval() if hasattr(self.gp, "likelihood") and self.gp.likelihood is not None: self.gp.likelihood.eval() - X_tensor = X_tensor.to(self.device, dtype=torch.float32) + X_tensor = X_tensor.to(self.device, dtype=torch.float64) with gpytorch.settings.fast_pred_var(): if hasattr(self.gp, "posterior"): post = self.gp.posterior(X_tensor) @@ -194,7 +194,7 @@ def run(self): # --- Step 1: suggest candidates --- t0 = time.time() - next_points = self.suggest_next(self.gp) # torch.float32, [n, d], on device + next_points = self.suggest_next(self.gp) # torch.float64, [n, d], on device t_next = time.time() # --- Step 1b: pre-evaluation predictions for diagnostics --- @@ -215,7 +215,7 @@ def run(self): # --- Step 2: single-sample evaluations (objective & true pl) --- y_list, pl_list = [], [] next_points_np = ( - next_points.detach().round().clamp(0, 1).cpu().numpy().astype("int64") + next_points.detach().round().cpu().numpy().astype("int64") ) for i in range(next_points_np.shape[0]): y_i, pl_i = self.objective_function(next_points_np[i]) diff --git a/code_construction/__pycache__/code_construction.cpython-311.pyc b/code_construction/__pycache__/code_construction.cpython-311.pyc deleted file mode 100644 index b76101e544850ba62f6fad2f92b505f856140212..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35946 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 From c54fd1096136eeb825dff18658f30537107eb384 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Thu, 16 Apr 2026 12:12:25 +0100 Subject: [PATCH 14/36] formatting + removed gbb codes (not valid) --- BOplayground.py | 79 --------------- bayesian_optimization/bo.py | 4 +- code_construction/code_construction.py | 43 +------- read_results.ipynb | 2 +- tests/test_gb_codes.ipynb | 130 ++++++++++++++----------- 5 files changed, 77 insertions(+), 181 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index c1858c2..79cc3f0 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -48,10 +48,6 @@ def __init__( match method: case "gb": self.mutate = self.swap_factors - case "gbb": - self.mutate = ( - self.swap_neighbours - ) # or self.swap_neighbours_within_matrices case "bb": self.mutate = self.flip_neighbours case _: @@ -68,22 +64,6 @@ def flip_neighbours(self, x: torch.Tensor) -> torch.Tensor: neigh_list.append(n) return torch.stack(neigh_list, dim=0) # [d, d] - # allows swapping between A and B - def swap_neighbours(self, x: torch.Tensor) -> torch.Tensor: - x = x.detach() - - ones_indices = torch.where(x[1:] == 1.0)[0] + 1 # dont swap anchor 1 at [0,0] - zeros_indices = torch.where(x == 0)[0] - - neigh_list = [] - for i in ones_indices: - for j in zeros_indices: - n = x.clone() - n[i] == 0.0 - n[j] == 1.0 - neigh_list.append(n) - return torch.stack(neigh_list, dim=0) - def swap_factors(self, x: torch.Tensor) -> torch.Tensor: x = x.detach() @@ -133,34 +113,6 @@ def swap_factors(self, x: torch.Tensor) -> torch.Tensor: return torch.stack(neigh_list, dim=0) - # only swaps within A and B so that both matrices always have the same number of 1s - def swap_neighbours_within_matrices(self, x: torch.Tensor) -> torch.Tensor: - x = x.detach() - half_length = x.size()[0] // 2 - - ones_indices_A = torch.where(x[1:half_length] == 1.0)[0] + 1 - zeros_indices_A = torch.where(x[:half_length] == 0)[0] - - neigh_list = [] - for i in ones_indices_A: - for j in zeros_indices_A: - n = x.clone() - n[i] == 0.0 - n[j] == 1.0 - neigh_list.append(n) - - ones_indices_B = torch.where(x[half_length:] == 1.0)[0] - zeros_indices_B = torch.where(x[half_length:] == 0)[0] - - for i in ones_indices_B: - for j in zeros_indices_B: - n = x.clone() - n[i] == 0.0 - n[j] == 1.0 - neigh_list.append(n) - - return torch.stack(neigh_list, dim=0) - @torch.no_grad() def __call__(self, gp) -> torch.Tensor: X0_np = self.gnp(self.next_points_num) # [n, d], 0/1 @@ -1063,8 +1015,6 @@ def get_new_points_function(self, number): new_points = self.get_new_gb_vector(number) elif self.method == "bb": new_points = self.get_new_bb_vector(number) - elif self.method == "gbb": - new_points = self.get_new_gbb_vector(number) return new_points def get_new_points_HGP(self, number): @@ -1200,33 +1150,6 @@ def get_new_bb_vector(self, number): number -= 1 return np.array(results) - def get_new_gbb_vector(self, number): - results = [] - l = self.code_constructor.para_dict["l"] - g = self.code_constructor.para_dict["g"] - - while number > 0: - new_point = np.zeros(shape=(2 * l * g)) - new_point[0] = 1 # fix x^0 * y^0 = 1 to ignore identical shifted codes - - indexes = np.random.permutation(l * g) - for i in range( - self.density - 1 - ): # density: number of 1s in both A and B, fixed to ensure sparsity - new_point[indexes[i]] = 1 - - np.random.shuffle(indexes) - for i in range(self.density): - new_point[indexes[i] + l * g] = 1 - - c = self.code_constructor.construct(new_point) - if c.k == 0: - continue - else: - results.append(new_point) - number -= 1 - return np.array(results) - if __name__ == "__main__": import pickle @@ -1297,8 +1220,6 @@ def get_new_gbb_vector(self, number): if code_class == "gb": gnp_obj.set_gx_mask() init_data_file = f"./data/BO_initial_points/GB_BO_initial_points_{dataset_index}_{lambda_}_{density}_{l}_{gnp_obj.gx_mask}.pkl" - elif code_class == "gbb": - init_data_file = f"./data/BO_initial_points/GBB_BO_initial_points_{dataset_index}_{lambda_}_{density}_{l}_{g}.pkl" else: # bb if l == 6 and g == 3: if lambda_ == 1: diff --git a/bayesian_optimization/bo.py b/bayesian_optimization/bo.py index 0c1622b..3089a0d 100644 --- a/bayesian_optimization/bo.py +++ b/bayesian_optimization/bo.py @@ -214,9 +214,7 @@ def run(self): # --- Step 2: single-sample evaluations (objective & true pl) --- y_list, pl_list = [], [] - next_points_np = ( - next_points.detach().round().cpu().numpy().astype("int64") - ) + next_points_np = next_points.detach().round().cpu().numpy().astype("int64") for i in range(next_points_np.shape[0]): y_i, pl_i = self.objective_function(next_points_np[i]) y_list.append(float(y_i) if torch.is_tensor(y_i) else y_i) diff --git a/code_construction/code_construction.py b/code_construction/code_construction.py index d539e5b..2dd702c 100644 --- a/code_construction/code_construction.py +++ b/code_construction/code_construction.py @@ -105,9 +105,8 @@ def check_parameters_validity(self): "bivariate-bycicle", "bb", "symmetric-qc-gb", - "gbb", ]: - if self.method in ["bb", "bivariate-bycicle", "gbb"]: + if self.method in ["bb", "bivariate-bycicle"]: try: self.n = self.para_dict["l"] * self.para_dict["g"] self.nx = self.n @@ -143,8 +142,6 @@ def construct(self, parameters): return self.quasi_cyclic_generalized_bicycle_code(parameters) elif self.method == "bivariate-bycicle" or self.method == "bb": return self.arbitrary_bivariate_bicycle_code(parameters) - elif self.method == "gbb": - return self.generalised_bivariate_bicycle_code(parameters) elif self.method == "gb": return self.generalised_bicycle_code(parameters) elif self.method == "symmetric-qc-gb": @@ -468,15 +465,15 @@ def gx_mask_to_bin(gx_mask, fs, l) -> int: gx_bin = CodeConstructor.multiply_polynomials_mod_l(gx_bin, fs[i], l) return gx_bin - + def _build_circulant_matrix(self, row) -> np.ndarray: length = len(row) col_idx = np.arange(length)[None, :] row_idx = np.arange(length)[:, None] - + shift_matrix = (col_idx - row_idx) % length return row[shift_matrix] - + def _build_bicycle_css_code(self, A, B) -> CSSCode: HX = np.hstack((A, B)) HZ = np.hstack((B.T, A.T)) @@ -671,38 +668,6 @@ def arbitrary_bivariate_bicycle_code(self, parameters): return self.quasi_cyclic_generalized_bicycle_code({"A": A, "B": B}) - def generalised_bivariate_bicycle_code(self, parameters): - l = self.para_dict["l"] - g = self.para_dict["g"] - - a = parameters[: (l * g)] - b = parameters[(l * g) :] - - A = np.zeros((l, l, g), dtype=int) - B = np.zeros((l, l, g), dtype=int) - - for i in range(l): - for j in range(l): - for k in range(g): - idx = (j * g) + k - if a[idx] == 1: - A[i][(i + j) % l][k] = 1 - if b[idx] == 1: - B[i][(i + j) % l][k] = 1 - - # untested vectorised optimisation: - # a_base = parameters[: (l * g)].reshape(l, g) - # b_base = parameters[(l * g) :].reshape(l, g) - - # row_idx = np.arange(l)[:, None] - # col_idx = np.arange(l)[None, :] - # shift_matrix = (col_idx - row_idx) % l - - # A = a_base[shift_matrix] - # B = b_base[shift_matrix] - - return self.quasi_cyclic_generalized_bicycle_code({"A": A, "B": B}) - def symmetric_quasi_cyclic_generalized_bicycle_code(self, A): B = A r = A.shape[0] diff --git a/read_results.ipynb b/read_results.ipynb index bd4f28b..9a89323 100644 --- a/read_results.ipynb +++ b/read_results.ipynb @@ -10,7 +10,7 @@ "import pickle\n", "import numpy as np\n", "\n", - "file_path = \"./data/BO_results/gbb_BO_12_6_0_42_1.pkl\"" + "file_path = \"./data/BO_results/GB_BO_results_0_1_4_7_1.pkl\"" ] }, { diff --git a/tests/test_gb_codes.ipynb b/tests/test_gb_codes.ipynb index bde2405..edf96a9 100644 --- a/tests/test_gb_codes.ipynb +++ b/tests/test_gb_codes.ipynb @@ -18,7 +18,7 @@ " add = \"x\"\n", " else:\n", " add = f\"x^{i}\"\n", - " \n", + "\n", " if poly == \"\":\n", " poly = add\n", " else:\n", @@ -43,7 +43,7 @@ "path_root = Path.cwd().parent\n", "\n", "if str(path_root) not in sys.path:\n", - " sys.path.append(str(path_root))\n" + " sys.path.append(str(path_root))" ] }, { @@ -56,8 +56,8 @@ "# TEST: does multiply_polynomials_mod_l correctly multiply polynomials with coefficients mod 2 and exponents mod l\n", "from code_construction.code_construction import CodeConstructor, CSSCode\n", "\n", - "p1 = 417 # 110100001 -> 1 + x^5 + x^7 + x^8\n", - "p2 = 9 # 1001 -> 1 + x^3 \n", + "p1 = 417 # 110100001 -> 1 + x^5 + x^7 + x^8\n", + "p2 = 9 # 1001 -> 1 + x^3\n", "\n", "l = 9\n", "\n", @@ -82,7 +82,7 @@ "source": [ "# TEST: does gx_mask_to_bin correctly generate the g(x) polynomial in int form given a list of factors and a bitmask\n", "fs = [\n", - " 5, # 101 -> 1+x^2\n", + " 5, # 101 -> 1+x^2\n", " 49, # 110001 -> 1+x^4+x^5\n", " 17, # 10001 -> 1+x^4\n", "]\n", @@ -99,7 +99,7 @@ "\n", "assert int_to_poly(gx_bin) == \"x^5 + x^8\"\n", "\n", - "print(\"Test Passed!\")\n" + "print(\"Test Passed!\")" ] }, { @@ -112,31 +112,26 @@ "# TEST: are randomly generated GB codes valid\n", "from BOplayground import Get_new_points_function\n", "\n", + "\n", "# this is the same as multiply_polynomials_mod_l but without the mod l\n", "def binary_poly_mul(p1, p2):\n", " res = 0\n", " for i in range(p2.bit_length()):\n", " if (p2 >> i) & 1:\n", - " res ^= (p1 << i)\n", + " res ^= p1 << i\n", " return res\n", "\n", "\n", "para_dict = {\"l\": l}\n", - "code_class = 'gb'\n", + "code_class = \"gb\"\n", "target_weight = 3\n", "desired_k = 12\n", "\n", - "cc = CodeConstructor(\n", - " method=code_class,\n", - " para_dict=para_dict\n", - ")\n", + "cc = CodeConstructor(method=code_class, para_dict=para_dict)\n", "\n", "\n", "gnp = Get_new_points_function(\n", - " method=code_class,\n", - " code_constructor=cc,\n", - " density=target_weight,\n", - " desired_k=desired_k\n", + " method=code_class, code_constructor=cc, density=target_weight, desired_k=desired_k\n", ")\n", "\n", "gnp.set_gx_mask()\n", @@ -153,12 +148,17 @@ " for f in fs:\n", " prod_fs = binary_poly_mul(prod_fs, f)\n", "\n", - "\n", " assert prod_fs == x_exp_l_minus_1\n", "\n", " gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, l)\n", - " assert CodeConstructor.multiply_polynomials_mod_l(gx_bin, qa, l).bit_count() == target_weight # LHS: a(x)\n", - " assert CodeConstructor.multiply_polynomials_mod_l(gx_bin, qb, l).bit_count() == target_weight # LHD: b(x)\n", + " assert (\n", + " CodeConstructor.multiply_polynomials_mod_l(gx_bin, qa, l).bit_count()\n", + " == target_weight\n", + " ) # LHS: a(x)\n", + " assert (\n", + " CodeConstructor.multiply_polynomials_mod_l(gx_bin, qb, l).bit_count()\n", + " == target_weight\n", + " ) # LHD: b(x)\n", "\n", "print(\"Test Passed!\")" ] @@ -196,15 +196,17 @@ "source": [ "from sympy import symbols, factor, expand, GF\n", "\n", + "\n", "def factor_poly(poly):\n", " factors = factor(poly, domain=GF(2))\n", " return factors\n", "\n", - "x = symbols('x')\n", - "print(factor_poly(1+x+x**5))\n", "\n", - "print(expand((1 + x**2 + x**3)*(1 + x), modulus=2))\n", - "print(expand((1 + x + x**2)*(1 + x), modulus=2))\n" + "x = symbols(\"x\")\n", + "print(factor_poly(1 + x + x**5))\n", + "\n", + "print(expand((1 + x**2 + x**3) * (1 + x), modulus=2))\n", + "print(expand((1 + x + x**2) * (1 + x), modulus=2))" ] }, { @@ -216,10 +218,11 @@ "source": [ "# TEST: is the CSS code generated by CodeConstructor correct for a given GB internal representation\n", "import numpy as np\n", - "a = 23 # 000010111 -> 1 + x + x^2 + x^4\n", + "\n", + "a = 23 # 000010111 -> 1 + x + x^2 + x^4\n", "b = 9 # 000001001 -> 1 + x^3\n", "\n", - "qa = 13 # 1101 -> 1 + x^2 + x^3\n", + "qa = 13 # 1101 -> 1 + x^2 + x^3\n", "qb = 7 # 111 -> 1 + x + x^2\n", "gx_mask = 1 # 001, gx_bin=3\n", "fs = [3, 7, 73] # from l=9 code above\n", @@ -230,32 +233,38 @@ "\n", "x = [gx_mask, qa, qb] + fs\n", "\n", - "a_lsb_first = [0,0,0,0,1,0,1,1,1][::-1] # bit order is flipped for A&B matrices (least significant bit on the left)\n", - "b_lsb_first = [0,0,0,0,0,1,0,0,1][::-1]\n", - "\n", - "expected_A = np.array([\n", - " a_lsb_first,\n", - " np.roll(a_lsb_first, 1),\n", - " np.roll(a_lsb_first, 2),\n", - " np.roll(a_lsb_first, 3),\n", - " np.roll(a_lsb_first, 4),\n", - " np.roll(a_lsb_first, 5),\n", - " np.roll(a_lsb_first, 6),\n", - " np.roll(a_lsb_first, 7),\n", - " np.roll(a_lsb_first, 8),\n", - "])\n", - "\n", - "expected_B = np.array([\n", - " b_lsb_first,\n", - " np.roll(b_lsb_first, 1),\n", - " np.roll(b_lsb_first, 2),\n", - " np.roll(b_lsb_first, 3),\n", - " np.roll(b_lsb_first, 4),\n", - " np.roll(b_lsb_first, 5),\n", - " np.roll(b_lsb_first, 6),\n", - " np.roll(b_lsb_first, 7),\n", - " np.roll(b_lsb_first, 8),\n", - "])\n", + "a_lsb_first = [0, 0, 0, 0, 1, 0, 1, 1, 1][\n", + " ::-1\n", + "] # bit order is flipped for A&B matrices (least significant bit on the left)\n", + "b_lsb_first = [0, 0, 0, 0, 0, 1, 0, 0, 1][::-1]\n", + "\n", + "expected_A = np.array(\n", + " [\n", + " a_lsb_first,\n", + " np.roll(a_lsb_first, 1),\n", + " np.roll(a_lsb_first, 2),\n", + " np.roll(a_lsb_first, 3),\n", + " np.roll(a_lsb_first, 4),\n", + " np.roll(a_lsb_first, 5),\n", + " np.roll(a_lsb_first, 6),\n", + " np.roll(a_lsb_first, 7),\n", + " np.roll(a_lsb_first, 8),\n", + " ]\n", + ")\n", + "\n", + "expected_B = np.array(\n", + " [\n", + " b_lsb_first,\n", + " np.roll(b_lsb_first, 1),\n", + " np.roll(b_lsb_first, 2),\n", + " np.roll(b_lsb_first, 3),\n", + " np.roll(b_lsb_first, 4),\n", + " np.roll(b_lsb_first, 5),\n", + " np.roll(b_lsb_first, 6),\n", + " np.roll(b_lsb_first, 7),\n", + " np.roll(b_lsb_first, 8),\n", + " ]\n", + ")\n", "\n", "expected_hx = np.hstack((expected_A, expected_B))\n", "\n", @@ -290,7 +299,7 @@ "# l = None\n", "\n", "# fs = []\n", - "qa = 3 # 11 -> 1 + x\n", + "qa = 3 # 11 -> 1 + x\n", "qb = 5 # 111 -> 1 + x + x^2\n", "gx_mask = 3 # 001\n", "\n", @@ -306,10 +315,7 @@ "target_weight2 = 4\n", "\n", "gnp2 = Get_new_points_function(\n", - " method=code_class,\n", - " code_constructor=cc,\n", - " density=target_weight2,\n", - " desired_k=desired_k\n", + " method=code_class, code_constructor=cc, density=target_weight2, desired_k=desired_k\n", ")\n", "\n", "gnp2.set_gx_mask()\n", @@ -319,7 +325,7 @@ " gnp=gnp2.get_new_points_function,\n", " acquisition=None,\n", " l=l,\n", - " target_row_weight=target_weight2\n", + " target_row_weight=target_weight2,\n", ")\n", "\n", "x_tensor = torch.tensor(x, dtype=torch.float32)\n", @@ -334,8 +340,14 @@ " assert (n[1] == qa) or (n[2] == qb)\n", " assert np.array_equal(n[3:], fs)\n", "\n", - " assert CodeConstructor.multiply_polynomials_mod_l(gx_bin, n[1], l).bit_count() == target_weight2\n", - " assert CodeConstructor.multiply_polynomials_mod_l(gx_bin, n[2], l).bit_count() == target_weight2\n", + " assert (\n", + " CodeConstructor.multiply_polynomials_mod_l(gx_bin, n[1], l).bit_count()\n", + " == target_weight2\n", + " )\n", + " assert (\n", + " CodeConstructor.multiply_polynomials_mod_l(gx_bin, n[2], l).bit_count()\n", + " == target_weight2\n", + " )\n", " assert n != x\n", "\n", " cc.construct(n) # CSSCode has built in checks to make sure the code is valid\n", From 2fa381fb1a681ae05e219ebf13bf3be42e1a73e8 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Thu, 16 Apr 2026 19:26:42 +0100 Subject: [PATCH 15/36] added a new standard normalizer for a mode where the GP predicts distance (which does not need to be log scaled) rather than LER --- BOplayground.py | 165 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 138 insertions(+), 27 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index 79cc3f0..f068863 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -2,6 +2,8 @@ DEVICE = "cuda" import os import random +from abc import ABC, abstractmethod +from __future__ import annotations import numpy as np import torch from torch.nn.utils import clip_grad_norm_ @@ -337,15 +339,65 @@ def __call__(self, X, gp: torch.nn.Module): @dataclass -class LogStdStats: - """Stores the fitted statistics (mean, std, eps) for log-domain normalization.""" +class StdStats: + mean: float + scale: float - mean: float # mean of log(pl + eps) - scale: float # std of log(pl + eps) - eps: float # epsilon used in log(pl + eps) +@dataclass +class LogStdStats(StdStats): + eps: float + + +class Normalizer(ABC): + def __init__( + self, + device: str = "cpu", + ): + self._fitted: bool = False + self.device = device + + def _to_tensor(self, x: TensorLike, dtype=torch.float32) -> torch.Tensor: + """Convert input (np.ndarray, float, or Tensor) to a torch.Tensor on the correct device.""" + if isinstance(x, torch.Tensor): + return x.to(self.device, dtype=dtype) + arr = np.asarray(x) + return torch.tensor(arr, device=self.device, dtype=dtype) + + @abstractmethod + def fit(self, y: TensorLike) -> Normalizer: + pass + + @abstractmethod + def transform(self, y: TensorLike) -> torch.Tensor: + pass + + @abstractmethod + def inverse_transform(self, z: TensorLike) -> torch.Tensor: + pass + + @abstractmethod + def inverse_mean_std( + self, mu_z: TensorLike, std_z: TensorLike + ) -> Tuple[torch.Tensor, torch.Tensor]: + pass + + def is_fitted(self) -> bool: + """Return whether the normalizer has been fitted.""" + return self._fitted + def get_stats(self) -> LogStdStats: + """Return the current (mean, scale, eps) statistics.""" + return self.stats + + def state_dict(self) -> Dict[str, Any]: + """Serialize current state to a Python dict.""" + pass -class LogStdNormalizer: + def load_state_dict(self, state: Dict[str, Any]): + """Restore normalizer state from a dict.""" + pass + +class LogStdNormalizer(Normalizer): """ A normalizer that transforms probabilities `pl ∈ (0,1)` into a standardized latent space via: @@ -360,19 +412,12 @@ def __init__( max_prob: float = 1.0 - 1e-12, device: str = "cpu", ): + super().__init__(device) self.stats: LogStdStats = LogStdStats(mean=0.0, scale=1.0, eps=float(eps)) - self._fitted: bool = False self.min_prob = float(min_prob) self.max_prob = float(max_prob) - self.device = device # -------------------- Basic utilities -------------------- - def _to_tensor(self, x: TensorLike, dtype=torch.float32) -> torch.Tensor: - """Convert input (np.ndarray, float, or Tensor) to a torch.Tensor on the correct device.""" - if isinstance(x, torch.Tensor): - return x.to(self.device, dtype=dtype) - arr = np.asarray(x) - return torch.tensor(arr, device=self.device, dtype=dtype) def _clamp_prob(self, pl: torch.Tensor) -> torch.Tensor: """Clamp probabilities to [min_prob, max_prob] for numerical stability.""" @@ -380,11 +425,11 @@ def _clamp_prob(self, pl: torch.Tensor) -> torch.Tensor: # -------------------- Fitting and transforms -------------------- @torch.no_grad() - def fit(self, pl: TensorLike) -> "LogStdNormalizer": + def fit(self, y: TensorLike) -> LogStdNormalizer: """ Fit mean and scale parameters from all observed probabilities (in log-domain). """ - pl_t = self._to_tensor(pl, dtype=torch.float32).view(-1) + pl_t = self._to_tensor(y, dtype=torch.float32).view(-1) pl_t = self._clamp_prob(pl_t) ylog = torch.log(pl_t + self.stats.eps) mean = ylog.mean().item() @@ -399,13 +444,13 @@ def fit(self, pl: TensorLike) -> "LogStdNormalizer": return self @torch.no_grad() - def transform(self, pl: TensorLike) -> torch.Tensor: + def transform(self, y: TensorLike) -> torch.Tensor: """ Transform from probability domain → standardized latent domain: z = (log(pl + eps) - mean) / scale """ assert self._fitted, "Call fit(pl_train) before transform." - pl_t = self._to_tensor(pl, dtype=torch.float32) + pl_t = self._to_tensor(y, dtype=torch.float32) pl_t = self._clamp_prob(pl_t) ylog = torch.log(pl_t + self.stats.eps) z = (ylog - self.stats.mean) / self.stats.scale @@ -461,20 +506,12 @@ def inverse_mean_std( return mean_pl, std_pl # -------------------- State handling and serialization -------------------- - def is_fitted(self) -> bool: - """Return whether the normalizer has been fitted.""" - return self._fitted - - def get_stats(self) -> LogStdStats: - """Return the current (mean, scale, eps) statistics.""" - return self.stats def set_eps(self, eps: float): """Update epsilon used in log(pl + eps).""" self.stats.eps = float(eps) def state_dict(self) -> Dict[str, Any]: - """Serialize current state to a Python dict.""" return { "stats": asdict(self.stats), "fitted": self._fitted, @@ -484,7 +521,6 @@ def state_dict(self) -> Dict[str, Any]: } def load_state_dict(self, state: Dict[str, Any]): - """Restore normalizer state from a dict.""" s = state["stats"] self.stats = LogStdStats( mean=float(s["mean"]), scale=float(s["scale"]), eps=float(s["eps"]) @@ -494,6 +530,81 @@ def load_state_dict(self, state: Dict[str, Any]): self.max_prob = float(state.get("max_prob", self.max_prob)) self.device = state.get("device", self.device) +class StdNormalizer(Normalizer): + """ + A normalizer for distance (d) which standardizes values to + z = (d - mean) / std_dev + + As error rate (pl) scales exponentially with d, LogStdNormalizer is used for pl + and linear standardization is used for d. + """ + + def __init__(self, device): + super().__init__(device) + self.stats: StdStats = StdStats(mean=0.0, scale=1.0) + + + @torch.no_grad() + def fit(self, y: TensorLike) -> StdNormalizer: + d_t = self._to_tensor(y, dtype=torch.float32).view(-1) + mean = d_t.mean().item() + std = d_t.std(unbiased=False).item() + + if std < 1e-12: + std = 1.0 + + self.stats = StdStats(mean=float(mean), scale=float(std)) + self._fitted = True + return self + + + @torch.no_grad() + def transform(self, y: TensorLike) -> torch.Tensor: + assert self._fitted, "Call fit(d_train) before transform." + + d_t = self._to_tensor(y, dtype=torch.float32) + z = (d_t - self.stats.mean) / self.stats.scale + return z + + + @torch.no_grad() + def inverse_transform(self, z: TensorLike) -> torch.Tensor: + assert self._fitted, "Call fit(d_train) before inverse_transform." + + z_t = self._to_tensor(z, dtype=torch.float32) + d = z_t * self.stats.scale + self.stats.mean + return d + + + @torch.no_grad() + def inverse_mean_std( + self, mu_z: TensorLike, std_z: TensorLike + ) -> Tuple[torch.Tensor, torch.Tensor]: + assert self._fitted, "Call fit(d_train) before inverse_mean_std." + + mu_z_t = self._to_tensor(mu_z, dtype=torch.float32) + std_z_t = self._to_tensor(std_z, dtype=torch.float32).abs() + + mu = mu_z_t * self.stats.scale + self.stats.mean + std = std_z_t * abs(self.stats.scale) + + return mu, std + + def state_dict(self) -> Dict[str, Any]: + return { + "stats": asdict(self.stats), + "fitted": self._fitted, + "device": self.device, + } + + def load_state_dict(self, state: Dict[str, Any]): + s = state["stats"] + self.stats = StdStats( + mean=float(s["mean"]), scale=float(s["scale"]) + ) + self._fitted = bool(state.get("fitted", True)) + self.device = state.get("device", self.device) + class GPTrainer: """ From 794e4b88274a65e54218a2cfbdaaab2e61e50450 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Thu, 16 Apr 2026 19:31:59 +0100 Subject: [PATCH 16/36] moved to new file + formatting --- BOplayground.py | 277 +----------------------------------------------- Normalizer.py | 276 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 278 insertions(+), 275 deletions(-) create mode 100644 Normalizer.py diff --git a/BOplayground.py b/BOplayground.py index f068863..1cd17ea 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -2,8 +2,6 @@ DEVICE = "cuda" import os import random -from abc import ABC, abstractmethod -from __future__ import annotations import numpy as np import torch from torch.nn.utils import clip_grad_norm_ @@ -16,13 +14,13 @@ from bayesian_optimization.chaincomplexembedding import * from bayesian_optimization.gp import * from bayesian_optimization.bo import BO_on_QEC +from Normalizer import LogStdNormalizer import gpytorch from gpytorch.mlls import ExactMarginalLogLikelihood from gpytorch.kernels import RBFKernel, MaternKernel, SpectralMixtureKernel, ScaleKernel import copy -from typing import Dict, Optional, Tuple, Any, List, Union -from dataclasses import dataclass, asdict +from typing import Dict, Optional, Tuple, Any, List class HillClimbing: @@ -335,277 +333,6 @@ def __call__(self, X, gp: torch.nn.Module): return ei -TensorLike = Union[torch.Tensor, np.ndarray, float] - - -@dataclass -class StdStats: - mean: float - scale: float - -@dataclass -class LogStdStats(StdStats): - eps: float - - -class Normalizer(ABC): - def __init__( - self, - device: str = "cpu", - ): - self._fitted: bool = False - self.device = device - - def _to_tensor(self, x: TensorLike, dtype=torch.float32) -> torch.Tensor: - """Convert input (np.ndarray, float, or Tensor) to a torch.Tensor on the correct device.""" - if isinstance(x, torch.Tensor): - return x.to(self.device, dtype=dtype) - arr = np.asarray(x) - return torch.tensor(arr, device=self.device, dtype=dtype) - - @abstractmethod - def fit(self, y: TensorLike) -> Normalizer: - pass - - @abstractmethod - def transform(self, y: TensorLike) -> torch.Tensor: - pass - - @abstractmethod - def inverse_transform(self, z: TensorLike) -> torch.Tensor: - pass - - @abstractmethod - def inverse_mean_std( - self, mu_z: TensorLike, std_z: TensorLike - ) -> Tuple[torch.Tensor, torch.Tensor]: - pass - - def is_fitted(self) -> bool: - """Return whether the normalizer has been fitted.""" - return self._fitted - - def get_stats(self) -> LogStdStats: - """Return the current (mean, scale, eps) statistics.""" - return self.stats - - def state_dict(self) -> Dict[str, Any]: - """Serialize current state to a Python dict.""" - pass - - def load_state_dict(self, state: Dict[str, Any]): - """Restore normalizer state from a dict.""" - pass - -class LogStdNormalizer(Normalizer): - """ - A normalizer that transforms probabilities `pl ∈ (0,1)` into a standardized latent space via: - - z = (log(pl + eps) - mean) / scale - - """ - - def __init__( - self, - eps: float = 1e-8, - min_prob: float = 1e-12, - max_prob: float = 1.0 - 1e-12, - device: str = "cpu", - ): - super().__init__(device) - self.stats: LogStdStats = LogStdStats(mean=0.0, scale=1.0, eps=float(eps)) - self.min_prob = float(min_prob) - self.max_prob = float(max_prob) - - # -------------------- Basic utilities -------------------- - - def _clamp_prob(self, pl: torch.Tensor) -> torch.Tensor: - """Clamp probabilities to [min_prob, max_prob] for numerical stability.""" - return pl.clamp(self.min_prob, self.max_prob) - - # -------------------- Fitting and transforms -------------------- - @torch.no_grad() - def fit(self, y: TensorLike) -> LogStdNormalizer: - """ - Fit mean and scale parameters from all observed probabilities (in log-domain). - """ - pl_t = self._to_tensor(y, dtype=torch.float32).view(-1) - pl_t = self._clamp_prob(pl_t) - ylog = torch.log(pl_t + self.stats.eps) - mean = ylog.mean().item() - std = ylog.std(unbiased=False).item() # population standard deviation - - # Avoid degenerate scaling (very small std can cause instability) - if std < 1e-12: - std = 1.0 - - self.stats = LogStdStats(mean=float(mean), scale=float(std), eps=self.stats.eps) - self._fitted = True - return self - - @torch.no_grad() - def transform(self, y: TensorLike) -> torch.Tensor: - """ - Transform from probability domain → standardized latent domain: - z = (log(pl + eps) - mean) / scale - """ - assert self._fitted, "Call fit(pl_train) before transform." - pl_t = self._to_tensor(y, dtype=torch.float32) - pl_t = self._clamp_prob(pl_t) - ylog = torch.log(pl_t + self.stats.eps) - z = (ylog - self.stats.mean) / self.stats.scale - return z - - @torch.no_grad() - def inverse_transform(self, z: TensorLike) -> torch.Tensor: - """ - Deterministic inverse transform: - Given z, return point estimate of pl (not posterior expectation). - pl = exp(z * scale + mean) - eps - """ - assert self._fitted, "Call fit(pl_train) before inverse_transform." - z_t = self._to_tensor(z, dtype=torch.float32) - logpl = z_t * self.stats.scale + self.stats.mean - pl = torch.exp(logpl) - self.stats.eps - return self._clamp_prob(pl) - - # -------------------- Posterior inverse transform (used in EI acquisition) -------------------- - @torch.no_grad() - def inverse_mean_std( - self, mu_z: TensorLike, std_z: TensorLike - ) -> Tuple[torch.Tensor, torch.Tensor]: - """ - If z ~ N(mu_z, std_z^2), then log(pl + eps) ~ N(mu, std^2), where: - mu = mu_z * scale + mean - std = |scale| * std_z - - Using log-normal moment formulas: - E[pl + eps] = exp(mu + 0.5 * std^2) - Var(pl + eps) = (exp(std^2) - 1) * exp(2mu + std^2) - - Returns: - (mean_pl, std_pl): expected mean and std of pl in probability domain. - """ - assert self._fitted, "Call fit(pl_train) before inverse_mean_std." - mu_z_t = self._to_tensor(mu_z, dtype=torch.float32) - std_z_t = self._to_tensor(std_z, dtype=torch.float32).abs() - - mu = mu_z_t * self.stats.scale + self.stats.mean - std = std_z_t * abs(self.stats.scale) - - # E[pl + eps] and Var(pl + eps) - exp_half_var = torch.exp(0.5 * std**2) - mean_pl_plus = torch.exp(mu) * exp_half_var - var_pl_plus = (torch.exp(std**2) - 1.0) * torch.exp(2.0 * mu + std**2) - - mean_pl = mean_pl_plus - self.stats.eps - std_pl = var_pl_plus.clamp_min(1e-30).sqrt() - - # Clamp mean within valid probability range - mean_pl = self._clamp_prob(mean_pl) - return mean_pl, std_pl - - # -------------------- State handling and serialization -------------------- - - def set_eps(self, eps: float): - """Update epsilon used in log(pl + eps).""" - self.stats.eps = float(eps) - - def state_dict(self) -> Dict[str, Any]: - return { - "stats": asdict(self.stats), - "fitted": self._fitted, - "min_prob": self.min_prob, - "max_prob": self.max_prob, - "device": self.device, - } - - def load_state_dict(self, state: Dict[str, Any]): - s = state["stats"] - self.stats = LogStdStats( - mean=float(s["mean"]), scale=float(s["scale"]), eps=float(s["eps"]) - ) - self._fitted = bool(state.get("fitted", True)) - self.min_prob = float(state.get("min_prob", self.min_prob)) - self.max_prob = float(state.get("max_prob", self.max_prob)) - self.device = state.get("device", self.device) - -class StdNormalizer(Normalizer): - """ - A normalizer for distance (d) which standardizes values to - z = (d - mean) / std_dev - - As error rate (pl) scales exponentially with d, LogStdNormalizer is used for pl - and linear standardization is used for d. - """ - - def __init__(self, device): - super().__init__(device) - self.stats: StdStats = StdStats(mean=0.0, scale=1.0) - - - @torch.no_grad() - def fit(self, y: TensorLike) -> StdNormalizer: - d_t = self._to_tensor(y, dtype=torch.float32).view(-1) - mean = d_t.mean().item() - std = d_t.std(unbiased=False).item() - - if std < 1e-12: - std = 1.0 - - self.stats = StdStats(mean=float(mean), scale=float(std)) - self._fitted = True - return self - - - @torch.no_grad() - def transform(self, y: TensorLike) -> torch.Tensor: - assert self._fitted, "Call fit(d_train) before transform." - - d_t = self._to_tensor(y, dtype=torch.float32) - z = (d_t - self.stats.mean) / self.stats.scale - return z - - - @torch.no_grad() - def inverse_transform(self, z: TensorLike) -> torch.Tensor: - assert self._fitted, "Call fit(d_train) before inverse_transform." - - z_t = self._to_tensor(z, dtype=torch.float32) - d = z_t * self.stats.scale + self.stats.mean - return d - - - @torch.no_grad() - def inverse_mean_std( - self, mu_z: TensorLike, std_z: TensorLike - ) -> Tuple[torch.Tensor, torch.Tensor]: - assert self._fitted, "Call fit(d_train) before inverse_mean_std." - - mu_z_t = self._to_tensor(mu_z, dtype=torch.float32) - std_z_t = self._to_tensor(std_z, dtype=torch.float32).abs() - - mu = mu_z_t * self.stats.scale + self.stats.mean - std = std_z_t * abs(self.stats.scale) - - return mu, std - - def state_dict(self) -> Dict[str, Any]: - return { - "stats": asdict(self.stats), - "fitted": self._fitted, - "device": self.device, - } - - def load_state_dict(self, state: Dict[str, Any]): - s = state["stats"] - self.stats = StdStats( - mean=float(s["mean"]), scale=float(s["scale"]) - ) - self._fitted = bool(state.get("fitted", True)) - self.device = state.get("device", self.device) - - class GPTrainer: """ Trainer for an ExactGP with a fixed structure (e.g., ScaleKernel ∘ Matern). diff --git a/Normalizer.py b/Normalizer.py new file mode 100644 index 0000000..6972fbe --- /dev/null +++ b/Normalizer.py @@ -0,0 +1,276 @@ +from abc import ABC, abstractmethod +from __future__ import annotations +from dataclasses import dataclass, asdict + +import numpy as np +import torch + +from typing import Dict, Tuple, Any, Union + + +@dataclass +class StdStats: + mean: float + scale: float + + +@dataclass +class LogStdStats(StdStats): + eps: float + + +TensorLike = Union[torch.Tensor, np.ndarray, float] + + +class Normalizer(ABC): + def __init__( + self, + device: str = "cpu", + ): + self._fitted: bool = False + self.device = device + + def _to_tensor(self, x: TensorLike, dtype=torch.float32) -> torch.Tensor: + """Convert input (np.ndarray, float, or Tensor) to a torch.Tensor on the correct device.""" + if isinstance(x, torch.Tensor): + return x.to(self.device, dtype=dtype) + arr = np.asarray(x) + return torch.tensor(arr, device=self.device, dtype=dtype) + + @abstractmethod + def fit(self, y: TensorLike) -> Normalizer: + pass + + @abstractmethod + def transform(self, y: TensorLike) -> torch.Tensor: + pass + + @abstractmethod + def inverse_transform(self, z: TensorLike) -> torch.Tensor: + pass + + @abstractmethod + def inverse_mean_std( + self, mu_z: TensorLike, std_z: TensorLike + ) -> Tuple[torch.Tensor, torch.Tensor]: + pass + + def is_fitted(self) -> bool: + """Return whether the normalizer has been fitted.""" + return self._fitted + + def get_stats(self) -> LogStdStats: + """Return the current (mean, scale, eps) statistics.""" + return self.stats + + def state_dict(self) -> Dict[str, Any]: + """Serialize current state to a Python dict.""" + pass + + def load_state_dict(self, state: Dict[str, Any]): + """Restore normalizer state from a dict.""" + pass + + +class LogStdNormalizer(Normalizer): + """ + A normalizer that transforms probabilities `pl ∈ (0,1)` into a standardized latent space via: + + z = (log(pl + eps) - mean) / scale + + """ + + def __init__( + self, + eps: float = 1e-8, + min_prob: float = 1e-12, + max_prob: float = 1.0 - 1e-12, + device: str = "cpu", + ): + super().__init__(device) + self.stats: LogStdStats = LogStdStats(mean=0.0, scale=1.0, eps=float(eps)) + self.min_prob = float(min_prob) + self.max_prob = float(max_prob) + + # -------------------- Basic utilities -------------------- + + def _clamp_prob(self, pl: torch.Tensor) -> torch.Tensor: + """Clamp probabilities to [min_prob, max_prob] for numerical stability.""" + return pl.clamp(self.min_prob, self.max_prob) + + # -------------------- Fitting and transforms -------------------- + @torch.no_grad() + def fit(self, y: TensorLike) -> LogStdNormalizer: + """ + Fit mean and scale parameters from all observed probabilities (in log-domain). + """ + pl_t = self._to_tensor(y, dtype=torch.float32).view(-1) + pl_t = self._clamp_prob(pl_t) + ylog = torch.log(pl_t + self.stats.eps) + mean = ylog.mean().item() + std = ylog.std(unbiased=False).item() # population standard deviation + + # Avoid degenerate scaling (very small std can cause instability) + if std < 1e-12: + std = 1.0 + + self.stats = LogStdStats(mean=float(mean), scale=float(std), eps=self.stats.eps) + self._fitted = True + return self + + @torch.no_grad() + def transform(self, y: TensorLike) -> torch.Tensor: + """ + Transform from probability domain → standardized latent domain: + z = (log(pl + eps) - mean) / scale + """ + assert self._fitted, "Call fit(pl_train) before transform." + pl_t = self._to_tensor(y, dtype=torch.float32) + pl_t = self._clamp_prob(pl_t) + ylog = torch.log(pl_t + self.stats.eps) + z = (ylog - self.stats.mean) / self.stats.scale + return z + + @torch.no_grad() + def inverse_transform(self, z: TensorLike) -> torch.Tensor: + """ + Deterministic inverse transform: + Given z, return point estimate of pl (not posterior expectation). + pl = exp(z * scale + mean) - eps + """ + assert self._fitted, "Call fit(pl_train) before inverse_transform." + z_t = self._to_tensor(z, dtype=torch.float32) + logpl = z_t * self.stats.scale + self.stats.mean + pl = torch.exp(logpl) - self.stats.eps + return self._clamp_prob(pl) + + # -------------------- Posterior inverse transform (used in EI acquisition) -------------------- + @torch.no_grad() + def inverse_mean_std( + self, mu_z: TensorLike, std_z: TensorLike + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + If z ~ N(mu_z, std_z^2), then log(pl + eps) ~ N(mu, std^2), where: + mu = mu_z * scale + mean + std = |scale| * std_z + + Using log-normal moment formulas: + E[pl + eps] = exp(mu + 0.5 * std^2) + Var(pl + eps) = (exp(std^2) - 1) * exp(2mu + std^2) + + Returns: + (mean_pl, std_pl): expected mean and std of pl in probability domain. + """ + assert self._fitted, "Call fit(pl_train) before inverse_mean_std." + mu_z_t = self._to_tensor(mu_z, dtype=torch.float32) + std_z_t = self._to_tensor(std_z, dtype=torch.float32).abs() + + mu = mu_z_t * self.stats.scale + self.stats.mean + std = std_z_t * abs(self.stats.scale) + + # E[pl + eps] and Var(pl + eps) + exp_half_var = torch.exp(0.5 * std**2) + mean_pl_plus = torch.exp(mu) * exp_half_var + var_pl_plus = (torch.exp(std**2) - 1.0) * torch.exp(2.0 * mu + std**2) + + mean_pl = mean_pl_plus - self.stats.eps + std_pl = var_pl_plus.clamp_min(1e-30).sqrt() + + # Clamp mean within valid probability range + mean_pl = self._clamp_prob(mean_pl) + return mean_pl, std_pl + + # -------------------- State handling and serialization -------------------- + + def set_eps(self, eps: float): + """Update epsilon used in log(pl + eps).""" + self.stats.eps = float(eps) + + def state_dict(self) -> Dict[str, Any]: + return { + "stats": asdict(self.stats), + "fitted": self._fitted, + "min_prob": self.min_prob, + "max_prob": self.max_prob, + "device": self.device, + } + + def load_state_dict(self, state: Dict[str, Any]): + s = state["stats"] + self.stats = LogStdStats( + mean=float(s["mean"]), scale=float(s["scale"]), eps=float(s["eps"]) + ) + self._fitted = bool(state.get("fitted", True)) + self.min_prob = float(state.get("min_prob", self.min_prob)) + self.max_prob = float(state.get("max_prob", self.max_prob)) + self.device = state.get("device", self.device) + + +class StdNormalizer(Normalizer): + """ + A normalizer for distance (d) which standardizes values to + z = (d - mean) / std_dev + + As error rate (pl) scales exponentially with d, LogStdNormalizer is used for pl + and linear standardization is used for d. + """ + + def __init__(self, device): + super().__init__(device) + self.stats: StdStats = StdStats(mean=0.0, scale=1.0) + + @torch.no_grad() + def fit(self, y: TensorLike) -> StdNormalizer: + d_t = self._to_tensor(y, dtype=torch.float32).view(-1) + mean = d_t.mean().item() + std = d_t.std(unbiased=False).item() + + if std < 1e-12: + std = 1.0 + + self.stats = StdStats(mean=float(mean), scale=float(std)) + self._fitted = True + return self + + @torch.no_grad() + def transform(self, y: TensorLike) -> torch.Tensor: + assert self._fitted, "Call fit(d_train) before transform." + + d_t = self._to_tensor(y, dtype=torch.float32) + z = (d_t - self.stats.mean) / self.stats.scale + return z + + @torch.no_grad() + def inverse_transform(self, z: TensorLike) -> torch.Tensor: + assert self._fitted, "Call fit(d_train) before inverse_transform." + + z_t = self._to_tensor(z, dtype=torch.float32) + d = z_t * self.stats.scale + self.stats.mean + return d + + @torch.no_grad() + def inverse_mean_std( + self, mu_z: TensorLike, std_z: TensorLike + ) -> Tuple[torch.Tensor, torch.Tensor]: + assert self._fitted, "Call fit(d_train) before inverse_mean_std." + + mu_z_t = self._to_tensor(mu_z, dtype=torch.float32) + std_z_t = self._to_tensor(std_z, dtype=torch.float32).abs() + + mu = mu_z_t * self.stats.scale + self.stats.mean + std = std_z_t * abs(self.stats.scale) + + return mu, std + + def state_dict(self) -> Dict[str, Any]: + return { + "stats": asdict(self.stats), + "fitted": self._fitted, + "device": self.device, + } + + def load_state_dict(self, state: Dict[str, Any]): + s = state["stats"] + self.stats = StdStats(mean=float(s["mean"]), scale=float(s["scale"])) + self._fitted = bool(state.get("fitted", True)) + self.device = state.get("device", self.device) From 4ea5b182ca2e386c1514f66dc6e3724b3216184f Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Fri, 17 Apr 2026 18:44:16 +0100 Subject: [PATCH 17/36] updated objective function so flag can be used to select distance or LER evaluation --- BOplayground.py | 17 ++++++++-- bayesian_optimization/objective_function.py | 35 +++++++++++++++++---- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index 1cd17ea..06ba6c3 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -14,7 +14,7 @@ from bayesian_optimization.chaincomplexembedding import * from bayesian_optimization.gp import * from bayesian_optimization.bo import BO_on_QEC -from Normalizer import LogStdNormalizer +from Normalizer import LogStdNormalizer, StdNormalizer import gpytorch from gpytorch.mlls import ExactMarginalLogLikelihood @@ -1030,10 +1030,15 @@ def get_new_bb_vector(self, number): para_dict = {"l": l, "g": g} code_constructor = CodeConstructor(method=code_class, para_dict=para_dict) + code_eval_metric = "distance" # TODO add this to arg handling # define objective function pp = 0.05 Obj_Func = ObjectiveFunction( - code_constructor, lambda_=lambda_, pp=pp, decoder_param={"trail": 10_000} + code_constructor, + lambda_=lambda_, + pp=pp, + decoder_param={"trail": 10_000}, + code_eval_metric=code_eval_metric, ) obj_func = Obj_Func.forward pl_to_obj = Obj_Func.pl_to_obj_with_std @@ -1138,6 +1143,12 @@ def get_new_bb_vector(self, number): log_every=8, save_best_state=True, ) + + if code_eval_metric == "distance": + normalizer = StdNormalizer(device=DEVICE) + else: + normalizer = None + # acquisition function: acq = EIAcquisitionFunction( pl_to_obj_fn=pl_to_obj, @@ -1145,7 +1156,7 @@ def get_new_bb_vector(self, number): jitter=1e-2, eps=1e-9, device=DEVICE, - normalizer=None, + normalizer=normalizer, prob_lower=1e-12, prob_upper=1.0 - 1e-12, log_eps=1e-8, diff --git a/bayesian_optimization/objective_function.py b/bayesian_optimization/objective_function.py index 65dd19d..d860d24 100644 --- a/bayesian_optimization/objective_function.py +++ b/bayesian_optimization/objective_function.py @@ -5,6 +5,7 @@ from evaluation.circuit_level_noise import ( MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise, ) +import qldpc # ========================= @@ -242,12 +243,14 @@ def __init__( decoder_param={"trail": 10000, "max_error": 100}, circuit_level_noise=False, circuit_param=None, + code_eval_metric="LER", ): self.code_constructor = code_constructor self.n = int(code_constructor.n) self.pp = float(pp) self.decoder_param = dict(decoder_param) self.lambda_ = lambda_ + self.code_eval_metric = code_eval_metric # Converters self.pl_t_converter = pl_t_converter(self.n, p_phys=self.pp, use_pchip=True) @@ -283,7 +286,7 @@ def _to_np_bits(x): arr = np.asarray(x, dtype=np.int64) return arr - def ler(self, css): + def ler(self, css: CSSCode): """Compute the logical error rate, either via circuit-level or decoder-based simulation.""" if self.circuit_level_noise: mc = MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise( @@ -345,8 +348,17 @@ def psuedo_t(self, css): t_hat = self.pl_t_converter.pl_to_t(k=None, pl=pL) return float(t_hat), float(pL) + def distance(self, css: CSSCode) -> int: + code = qldpc.codes.CSSCode(css.hx, css.hz) + distance = code.get_distance() # this is expensive, TODO add some timeout mechanic to prevent hangs and do some benchmarking + + if distance <= 0: + distance = 1 + + return distance + def forward(self, x): - """Compute the scalar objective F(x) and the corresponding total pL.""" + """Compute the scalar objective F(x) and the corresponding total pL or distance.""" css = self.code_constructor.construct(self._to_np_bits(x)) k = int(css.k) self._last_k = k @@ -354,10 +366,21 @@ def forward(self, x): return float(-1.0), float(1.0) R = k / float(self.n) - t_hat, pL_total = self.psuedo_t(css) - f2_val = float(self.f2_converter.t_to_f2(t_hat)) - F = self.lambda_ * R + f2_val - 1.0 - return float(F), float(pL_total) + if self.code_eval_metric == "LER": + t_hat, pL_total = self.psuedo_t(css) + f2_val = float(self.f2_converter.t_to_f2(t_hat)) + F = self.lambda_ * R + f2_val - 1.0 + return float(F), float(pL_total) + elif self.code_eval_metric == "distance": + d = self.distance(css) + t = (d - 1) / 2 # correctable errors + f2_val = float(self.f2_converter.t_to_f2(t)) + F = self.lambda_ * R + f2_val - 1.0 + return float(F), float(d) + else: + raise ValueError( + f"Code evaluation metric '{self.code_eval_metric}' is not supported." + ) # --------------------------- # Uncertainty propagation: from (pL_mean, pL_std) → (F_mean, F_std) From e0ff09ba40e03ee2dc13d251fc0c4829f454943f Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Sat, 18 Apr 2026 17:01:48 +0100 Subject: [PATCH 18/36] renamed some references to pl in logging, removed clamp on logged distance when in distance mode --- BOplayground.py | 35 ++++++++++-------- bayesian_optimization/bo.py | 39 +++++++++++++++------ bayesian_optimization/objective_function.py | 1 + 3 files changed, 49 insertions(+), 26 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index 06ba6c3..a54900e 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -1001,11 +1001,19 @@ def get_new_bb_vector(self, number): lambda_ = 1 desired_k = None gx_mask = 1 + code_eval_metric = "LER" args = sys.argv[1:] if args[0].isalpha(): code_class = args.pop(0) + if args[0][0] == "-": # -l for LER or -d for distance + flag = args.pop(0)[1:] + if flag == "l": + code_eval_metric = "LER" + elif flag == "d": + code_eval_metric = "distance" + if len(args) >= 3: seed = int(args[1]) dataset_index = int(args[2]) @@ -1030,7 +1038,6 @@ def get_new_bb_vector(self, number): para_dict = {"l": l, "g": g} code_constructor = CodeConstructor(method=code_class, para_dict=para_dict) - code_eval_metric = "distance" # TODO add this to arg handling # define objective function pp = 0.05 Obj_Func = ObjectiveFunction( @@ -1051,30 +1058,27 @@ def get_new_bb_vector(self, number): gx_mask=gx_mask, ) gnp = gnp_obj.get_new_points_function - # initial points: - # init_num = 20 - # X_init = gnp(init_num) - # y_init = [] - # pl_init = [] - # for x in X_init: - # y,pl = obj_func(x) - # y_init.append(y) - # pl_init.append(pl) + + if code_eval_metric == "distance": + distance_path_flag = "_d" + else: + distance_path_flag = "" + if code_class == "gb": gnp_obj.set_gx_mask() - init_data_file = f"./data/BO_initial_points/GB_BO_initial_points_{dataset_index}_{lambda_}_{density}_{l}_{gnp_obj.gx_mask}.pkl" + init_data_file = f"./data/BO_initial_points/GB_BO_initial_points{distance_path_flag}_{dataset_index}_{lambda_}_{density}_{l}_{gnp_obj.gx_mask}.pkl" else: # bb if l == 6 and g == 3: if lambda_ == 1: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_1.0_63.pkl" + init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{dataset_index}_1.0_63.pkl" else: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}_63.pkl" + init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{dataset_index}_{lambda_}_63.pkl" else: if lambda_ == 1: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_1.0.pkl" + init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{dataset_index}_1.0.pkl" else: - init_data_file = f"./data/BO_initial_points/BO_initial_points_{dataset_index}_{lambda_}.pkl" + init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{dataset_index}_{lambda_}.pkl" # file with 63 suffix has (l,m)=(6,3). Otherwise (l,m)=(12,6) if not os.path.exists(init_data_file): @@ -1194,6 +1198,7 @@ def code_validator(candidate_np): description=f"{code_class.upper()}-BO (GP+EI+HC)", device=DEVICE, pretrain=True, + code_eval_metric=code_eval_metric, ) best_x, best_y, evaluation_history = bo.run() diff --git a/bayesian_optimization/bo.py b/bayesian_optimization/bo.py index 3089a0d..df10cf5 100644 --- a/bayesian_optimization/bo.py +++ b/bayesian_optimization/bo.py @@ -93,6 +93,7 @@ def __init__( description="", device="cpu", pretrain=True, + code_eval_metric="LER", ): self.gp = gp @@ -101,8 +102,11 @@ def __init__( self.acquisition_function = acquisition_function self.suggest_next = suggest_next self.device = device + self.code_eval_metric = code_eval_metric self.X = initial_X.to(self.device, dtype=torch.float64) + # if code_eval_metric is distance, pl is true distance rather than true pl (logical error rate) + # all similarly named pl variables will also refer to distance rather than LER (TODO fix varibale names) self.pl = initial_pl.to( self.device, dtype=torch.float32 ) # probability space labels @@ -182,10 +186,19 @@ def run(self): pbar = tqdm.tqdm(range(self.BO_iterations), desc=self.description) # TSV for online diagnostics (pred vs. true); easy to inspect externally. + if self.code_eval_metric == "LER": + eval_name = "pl" + elif self.code_eval_metric == "distance": + eval_name = "d" + else: + raise ValueError( + f"Code evaluation metric '{self.code_eval_metric}' is not supported." + ) + metrics_tsv = open("pred_vs_true.tsv", "a", encoding="utf-8") if metrics_tsv.tell() == 0: metrics_tsv.write( - "round\tidx\tmu_pl\tstd_pl\tpl_true\tr_pl\tz_true\tmu_z\tstd_z\tr_z\tcover68_pl\tcover95_pl\n" + f"round\tidx\tmu_{eval_name}\tstd_{eval_name}\t{eval_name}_true\tr_{eval_name}\tz_true\tmu_z\tstd_z\tr_z\tcover68_{eval_name}\tcover95_{eval_name}\n" ) for rnd in pbar: @@ -209,7 +222,11 @@ def run(self): mu_z_pred, std_z_pred ) ) - mu_pl_pred = mu_pl_pred.clamp(1e-12, 1.0 - 1e-12) + if self.code_eval_metric == "LER": + mu_pl_pred = mu_pl_pred.clamp(1e-12, 1.0 - 1e-12) + else: # code_eval_metric == "distance" + mu_pl_pred = mu_pl_pred.clamp_min(1e-12) + std_pl_pred = std_pl_pred.clamp_min(1e-12) # --- Step 2: single-sample evaluations (objective & true pl) --- @@ -256,24 +273,24 @@ def run(self): m_rec = { "round": int(rnd), "idx": int(i), - "mu_pl": float(mu_pl.item()), - "std_pl": float(sp.item()), - "pl_true": float(pl_t.item()), - "r_pl": r_pl, + f"mu_{eval_name}": float(mu_pl.item()), + f"std_{eval_name}": float(sp.item()), + f"{eval_name}_true": float(pl_t.item()), + f"r_{eval_name}": r_pl, "z_true": float(z_t.item()), "mu_z": float(mu_z.item()), "std_z": float(sz.item()), "r_z": r_z, - "cover68_pl": bool(cover68), - "cover95_pl": bool(cover95), + f"cover68_{eval_name}": bool(cover68), + f"cover95_{eval_name}": bool(cover95), } iter_metrics.append(m_rec) # Append a TSV line metrics_tsv.write( - f"{m_rec['round']}\t{m_rec['idx']}\t{m_rec['mu_pl']:.6e}\t{m_rec['std_pl']:.6e}\t" - f"{m_rec['pl_true']:.6e}\t{m_rec['r_pl']:.3f}\t{m_rec['z_true']:.6e}\t" + f"{m_rec['round']}\t{m_rec['idx']}\t{m_rec[f'mu_{eval_name}']:.6e}\t{m_rec[f'std_{eval_name}']:.6e}\t" + f"{m_rec[f'{eval_name}_true']:.6e}\t{m_rec[f'r_{eval_name}']:.3f}\t{m_rec['z_true']:.6e}\t" f"{m_rec['mu_z']:.6e}\t{m_rec['std_z']:.6e}\t{m_rec['r_z']:.3f}\t" - f"{int(m_rec['cover68_pl'])}\t{int(m_rec['cover95_pl'])}\n" + f"{int(m_rec[f'cover68_{eval_name}'])}\t{int(m_rec[f'cover95_{eval_name}'])}\n" ) metrics_tsv.flush() self.pred_vs_true_history.append(iter_metrics) diff --git a/bayesian_optimization/objective_function.py b/bayesian_optimization/objective_function.py index d860d24..fcb1393 100644 --- a/bayesian_optimization/objective_function.py +++ b/bayesian_optimization/objective_function.py @@ -253,6 +253,7 @@ def __init__( self.code_eval_metric = code_eval_metric # Converters + # TODO rename references to pl, as could be evaluating distance instead self.pl_t_converter = pl_t_converter(self.n, p_phys=self.pp, use_pchip=True) self.f2_converter = f2_converter(self.n, use_pchip=True) From d196990ef4e53861a59764b3dce25573e28b7eae Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Mon, 20 Apr 2026 17:10:26 +0100 Subject: [PATCH 19/36] improved arg parsing for upcoming features that require various options --- BOplayground.py | 122 +++++++++++++++++++++++++----------------------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index a54900e..d8d0164 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -21,6 +21,7 @@ from gpytorch.kernels import RBFKernel, MaternKernel, SpectralMixtureKernel, ScaleKernel import copy from typing import Dict, Optional, Tuple, Any, List +from argparse import ArgumentParser class HillClimbing: @@ -988,74 +989,77 @@ def get_new_bb_vector(self, number): number -= 1 return np.array(results) +def get_args(): + parser = ArgumentParser(description="Search for QECCs in a certain family using BO") + + parser.add_argument('-c', '--code-class', default='bb', help='code class to search with BO') + parser.add_argument('--distance-exact', action='store_true', help='use exact code distance rather than LER to evaluate candidate codes') + parser.add_argument('--distance-heuristic', nargs='+', help='use a specified distance heuristic and parameters used to evaluate candidate codes') + parser.add_argument('-s', '--seed', default=42, type=int, help='seed') + parser.add_argument('-d', '--dataset-index', default=0, type=int, help='index of the dataset of starting codes') + parser.add_argument('--lam', default=1, type=int, help='lambda parameter of the objective function') + parser.add_argument('--polynomial-size', default=[12, 6], type=int, nargs='+', help='BB and GB codes only: sizes of polynomials a(x) and b(x)') + parser.add_argument('--density', type=int, help='GB codes only: density of polynomials used to construct parity check matrices for some codes') + parser.add_argument('--desired-k', type=int, help='GB codes only: desired number of logical qubits') + parser.add_argument('--gx-mask', type=int, help='GB codes only: bitmask of irreducible factors of (x^l - 1) that make up g(x)') + + return parser.parse_args() + if __name__ == "__main__": import pickle - import sys - - # default values - code_class = "bb" - density = 4 - seed = 42 - dataset_index = 0 - lambda_ = 1 - desired_k = None - gx_mask = 1 - code_eval_metric = "LER" - - args = sys.argv[1:] - if args[0].isalpha(): - code_class = args.pop(0) - - if args[0][0] == "-": # -l for LER or -d for distance - flag = args.pop(0)[1:] - if flag == "l": - code_eval_metric = "LER" - elif flag == "d": - code_eval_metric = "distance" - - if len(args) >= 3: - seed = int(args[1]) - dataset_index = int(args[2]) - if len(args) >= 4: - lambda_ = float(args[3]) - if len(args) >= 5: - density = int(args[4]) - if len(args) >= 6: - if args[5][:2] == "k=": - desired_k = int(args[5][2:]) - elif args[5][:2] == "g=": - gx_mask = int(args[5][2:]) - - set_all_seeds(seed) - # l = 12 - l = 7 - g = 6 # g here is m in Bravyi et al's paper + + args = get_args() + + if args.distance_exact or args.distance_heuristic: + code_eval_metric = "distance" + else: + code_eval_metric = "LER" + + if args.distance_heuristic: + dist_method = args.distance_heuristic[0] + dist_params = None if not args.distance_heuristic[1:] else args.distance_heuristic[1:] + else: + dist_method = None + dist_params = None + + l = args.polynomial_size[0] + if len(args.polynomial_size) > 1: + g = args.polynomial_size[1] + else: + g = None + + set_all_seeds(args.seed) + print( - f"(l,g)=({l},{g}), dataset_index = {dataset_index}, seed={seed}, lambda = {lambda_}" + f"(l,g)=({l},{g}), dataset_index = {args.dataset_index}, seed={args.seed}, lambda = {args.lam}" ) para_dict = {"l": l, "g": g} - code_constructor = CodeConstructor(method=code_class, para_dict=para_dict) + code_constructor = CodeConstructor(method=args.code_class, para_dict=para_dict) # define objective function pp = 0.05 + Obj_Func = ObjectiveFunction( code_constructor, - lambda_=lambda_, + lambda_=args.lam, pp=pp, decoder_param={"trail": 10_000}, code_eval_metric=code_eval_metric, + dist_method=dist_method, # reccommend QDistEvol for BB codes + dist_params=dist_params, + dist_seed=args.seed ) obj_func = Obj_Func.forward pl_to_obj = Obj_Func.pl_to_obj_with_std # method of sampling new points gnp_obj = Get_new_points_function( - method=code_class, + method=args.code_class, code_constructor=code_constructor, - density=density, - desired_k=desired_k, - gx_mask=gx_mask, + density=args.density, + desired_k=args.desired_k, + gx_mask=args.gx_mask, ) gnp = gnp_obj.get_new_points_function @@ -1064,27 +1068,27 @@ def get_new_bb_vector(self, number): else: distance_path_flag = "" - if code_class == "gb": + if args.code_class == "gb": gnp_obj.set_gx_mask() - init_data_file = f"./data/BO_initial_points/GB_BO_initial_points{distance_path_flag}_{dataset_index}_{lambda_}_{density}_{l}_{gnp_obj.gx_mask}.pkl" + init_data_file = f"./data/BO_initial_points/GB_BO_initial_points{distance_path_flag}_{args.dataset_index}_{args.lam}_{args.density}_{l}_{gnp_obj.gx_mask}.pkl" else: # bb if l == 6 and g == 3: - if lambda_ == 1: - init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{dataset_index}_1.0_63.pkl" + if args.lam == 1: + init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{args.dataset_index}_1.0_63.pkl" else: - init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{dataset_index}_{lambda_}_63.pkl" + init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{args.dataset_index}_{args.lam}_63.pkl" else: - if lambda_ == 1: - init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{dataset_index}_1.0.pkl" + if args.lam == 1: + init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{args.dataset_index}_1.0.pkl" else: - init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{dataset_index}_{lambda_}.pkl" + init_data_file = f"./data/BO_initial_points/BO_initial_points{distance_path_flag}_{args.dataset_index}_{args.lam}.pkl" # file with 63 suffix has (l,m)=(6,3). Otherwise (l,m)=(12,6) if not os.path.exists(init_data_file): # no starting codes exist, so generate some: init_num = 20 - print(f"Generating and evaluating {init_num} initial {code_class} codes...") + print(f"Generating and evaluating {init_num} initial {args.code_class} codes...") X_init = gnp(init_num) y_init = [] pl_init = [] @@ -1178,9 +1182,9 @@ def code_validator(candidate_np): acquisition=acq, device=DEVICE, validator=code_validator, - method=code_class, + method=args.code_class, l=l, - target_row_weight=density, + target_row_weight=args.density, ) # assemble BO @@ -1195,7 +1199,7 @@ def code_validator(candidate_np): initial_pl=pl_init, initial_y=y_init, BO_iterations=bo_iterations, - description=f"{code_class.upper()}-BO (GP+EI+HC)", + description=f"{args.code_class.upper()}-BO (GP+EI+HC)", device=DEVICE, pretrain=True, code_eval_metric=code_eval_metric, From 7f58be0d58f694d6b5cd9fbf3c49ef3d4171bb77 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Mon, 20 Apr 2026 18:22:43 +0100 Subject: [PATCH 20/36] bug fixes and code to support distance estimation with codeDistance library --- BOplayground.py | 67 ++++++++++++++++----- Normalizer.py | 2 +- bayesian_optimization/objective_function.py | 23 ++++++- 3 files changed, 75 insertions(+), 17 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index d8d0164..e35983c 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -989,19 +989,54 @@ def get_new_bb_vector(self, number): number -= 1 return np.array(results) + def get_args(): parser = ArgumentParser(description="Search for QECCs in a certain family using BO") - parser.add_argument('-c', '--code-class', default='bb', help='code class to search with BO') - parser.add_argument('--distance-exact', action='store_true', help='use exact code distance rather than LER to evaluate candidate codes') - parser.add_argument('--distance-heuristic', nargs='+', help='use a specified distance heuristic and parameters used to evaluate candidate codes') - parser.add_argument('-s', '--seed', default=42, type=int, help='seed') - parser.add_argument('-d', '--dataset-index', default=0, type=int, help='index of the dataset of starting codes') - parser.add_argument('--lam', default=1, type=int, help='lambda parameter of the objective function') - parser.add_argument('--polynomial-size', default=[12, 6], type=int, nargs='+', help='BB and GB codes only: sizes of polynomials a(x) and b(x)') - parser.add_argument('--density', type=int, help='GB codes only: density of polynomials used to construct parity check matrices for some codes') - parser.add_argument('--desired-k', type=int, help='GB codes only: desired number of logical qubits') - parser.add_argument('--gx-mask', type=int, help='GB codes only: bitmask of irreducible factors of (x^l - 1) that make up g(x)') + parser.add_argument( + "-c", "--code-class", default="bb", help="code class to search with BO" + ) + parser.add_argument( + "--distance-exact", + action="store_true", + help="use exact code distance rather than LER to evaluate candidate codes", + ) + parser.add_argument( + "--distance-heuristic", + nargs="+", + help="use a specified distance heuristic and parameters used to evaluate candidate codes", + ) + parser.add_argument("-s", "--seed", default=42, type=int, help="seed") + parser.add_argument( + "-d", + "--dataset-index", + default=0, + type=int, + help="index of the dataset of starting codes", + ) + parser.add_argument( + "--lam", default=1, type=int, help="lambda parameter of the objective function" + ) + parser.add_argument( + "--polynomial-size", + default=[12, 6], + type=int, + nargs="+", + help="BB and GB codes only: sizes of polynomials a(x) and b(x)", + ) + parser.add_argument( + "--density", + type=int, + help="GB codes only: density of polynomials used to construct parity check matrices for some codes", + ) + parser.add_argument( + "--desired-k", type=int, help="GB codes only: desired number of logical qubits" + ) + parser.add_argument( + "--gx-mask", + type=int, + help="GB codes only: bitmask of irreducible factors of (x^l - 1) that make up g(x)", + ) return parser.parse_args() @@ -1018,10 +1053,12 @@ def get_args(): if args.distance_heuristic: dist_method = args.distance_heuristic[0] - dist_params = None if not args.distance_heuristic[1:] else args.distance_heuristic[1:] + dist_params = ( + {} if not args.distance_heuristic[1:] else args.distance_heuristic[1:] + ) else: dist_method = None - dist_params = None + dist_params = {} l = args.polynomial_size[0] if len(args.polynomial_size) > 1: @@ -1049,7 +1086,7 @@ def get_args(): code_eval_metric=code_eval_metric, dist_method=dist_method, # reccommend QDistEvol for BB codes dist_params=dist_params, - dist_seed=args.seed + dist_seed=args.seed, ) obj_func = Obj_Func.forward pl_to_obj = Obj_Func.pl_to_obj_with_std @@ -1088,7 +1125,9 @@ def get_args(): if not os.path.exists(init_data_file): # no starting codes exist, so generate some: init_num = 20 - print(f"Generating and evaluating {init_num} initial {args.code_class} codes...") + print( + f"Generating and evaluating {init_num} initial {args.code_class} codes..." + ) X_init = gnp(init_num) y_init = [] pl_init = [] diff --git a/Normalizer.py b/Normalizer.py index 6972fbe..530ba01 100644 --- a/Normalizer.py +++ b/Normalizer.py @@ -1,5 +1,5 @@ -from abc import ABC, abstractmethod from __future__ import annotations +from abc import ABC, abstractmethod from dataclasses import dataclass, asdict import numpy as np diff --git a/bayesian_optimization/objective_function.py b/bayesian_optimization/objective_function.py index fcb1393..5f76b99 100644 --- a/bayesian_optimization/objective_function.py +++ b/bayesian_optimization/objective_function.py @@ -6,7 +6,7 @@ MonteCarloEstimationOfLogicalErrorRateUnderCircuitLevelNoise, ) import qldpc - +import codedistance # ========================= # Utilities @@ -244,6 +244,9 @@ def __init__( circuit_level_noise=False, circuit_param=None, code_eval_metric="LER", + dist_method=None, # allows method from https://github.com/m-webster/codeDistancePYPI + dist_params={}, + dist_seed=None, ): self.code_constructor = code_constructor self.n = int(code_constructor.n) @@ -251,6 +254,9 @@ def __init__( self.decoder_param = dict(decoder_param) self.lambda_ = lambda_ self.code_eval_metric = code_eval_metric + self.dist_method = dist_method + self.dist_params = dist_params + self.dist_seed = dist_seed # Converters # TODO rename references to pl, as could be evaluating distance instead @@ -358,6 +364,16 @@ def distance(self, css: CSSCode) -> int: return distance + def approximate_distance(self, css: CSSCode) -> int: + res = codedistance.CSScodeDistance( + css.hx, + css.hz, + method=self.dist_method, + params=self.dist_params, + seed=self.dist_seed, + ) + return res["d"] + def forward(self, x): """Compute the scalar objective F(x) and the corresponding total pL or distance.""" css = self.code_constructor.construct(self._to_np_bits(x)) @@ -373,7 +389,10 @@ def forward(self, x): F = self.lambda_ * R + f2_val - 1.0 return float(F), float(pL_total) elif self.code_eval_metric == "distance": - d = self.distance(css) + if self.dist_method is None: # exact distance calculation + d = self.distance(css) + else: # distance heuristic + d = self.approximate_distance(css) t = (d - 1) / 2 # correctable errors f2_val = float(self.f2_converter.t_to_f2(t)) F = self.lambda_ * R + f2_val - 1.0 From c9ca1e7948db8959b7fd7179617b028faef32775 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Tue, 21 Apr 2026 13:06:52 +0100 Subject: [PATCH 21/36] added some tests for new distance and estimated distance evaluation modes --- tests/test_dist_eval.ipynb | 183 +++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 tests/test_dist_eval.ipynb diff --git a/tests/test_dist_eval.ipynb b/tests/test_dist_eval.ipynb new file mode 100644 index 0000000..9de51eb --- /dev/null +++ b/tests/test_dist_eval.ipynb @@ -0,0 +1,183 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "0", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "from pathlib import Path\n", + "\n", + "path_root = Path.cwd().parent\n", + "\n", + "if str(path_root) not in sys.path:\n", + " sys.path.append(str(path_root))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1", + "metadata": {}, + "outputs": [], + "source": [ + "# test normalizer\n", + "import numpy as np\n", + "from Normalizer import StdNormalizer\n", + "\n", + "DEVICE = \"cuda\"\n", + "normalizer = StdNormalizer(DEVICE)\n", + "\n", + "mean = 7\n", + "scale = 2\n", + "e = 0.01 # arbitrary error threshold for random test\n", + "\n", + "data = np.random.normal(mean, scale, 1000000)\n", + "\n", + "normalizer = normalizer.fit(data)\n", + "assert normalizer.is_fitted()\n", + "print(normalizer.stats.mean - mean)\n", + "print(normalizer.stats.scale - scale)\n", + "assert abs(normalizer.stats.mean - mean) < e\n", + "assert abs(normalizer.stats.scale - scale) < e\n", + "\n", + "data_point = np.random.normal(mean, scale, 1)[0]\n", + "transformed_dp = normalizer.transform(data_point).item()\n", + "print(f\"{data_point} -> {transformed_dp}\")\n", + "assert transformed_dp > -3 and transformed_dp < 3 # standard normal stddev is 1, 99% of the time should be within mean (0) += 3*sd\n", + "\n", + "inverse_transformed_dp = normalizer.inverse_transform(transformed_dp).item()\n", + "print(f\"{transformed_dp} -> {inverse_transformed_dp}\")\n", + "print(inverse_transformed_dp - data_point)\n", + "assert abs(inverse_transformed_dp - data_point) < e\n", + "\n", + "print(\"Normalizer tests passed!\")\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "# test changes in bo/bo\n", + "from bayesian_optimization.bo import BO_on_QEC\n", + "\n", + "bo = BO_on_QEC(\n", + " code_eval_metric='distance'\n", + ")\n", + "\n", + "# nothing really to test here" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "# test changes in bo/objective function\n", + "from bayesian_optimization.objective_function import ObjectiveFunction\n", + "from code_construction.code_construction import CSSCode\n", + "import qldpc\n", + "\n", + "code_class = \"bb\"\n", + "class DummyCodeConstructor:\n", + " def __init__(self):\n", + " self.n = 1\n", + "\n", + " # def construct(self, code: qldpc.codes.CSSCode):\n", + " # return CSSCode(code.matrix_x, code.matrix_z)\n", + "\n", + " def construct(self, matrices):\n", + " return CSSCode(matrices[0], matrices[1])\n", + " \n", + "cc = DummyCodeConstructor()\n", + "\n", + "# exact distance\n", + "obj = ObjectiveFunction(\n", + " code_constructor=cc,\n", + " code_eval_metric=\"distance\"\n", + ")\n", + "\n", + "small_code = qldpc.codes.SteaneCode()\n", + "small_code_matrices = np.array([small_code.matrix_x, small_code.matrix_z])\n", + "small_known_distance = 3\n", + "_, d = obj.forward(small_code_matrices)\n", + "assert d == small_known_distance\n", + "\n", + "print(\"Exact distance test passed!\")\n", + "print()\n", + "\n", + "\n", + "# distance estimation with various methods\n", + "L = 10\n", + "large_code = qldpc.codes.ToricCode(L)\n", + "large_code_matrices = np.array([large_code.matrix_x, large_code.matrix_z])\n", + "\n", + "large_known_distance = L\n", + "# commented out methods have issues (require license, local binaries missing etc.)\n", + "methods = [\n", + " 'QDistEvol',\n", + " # 'magmaMinWeight',\n", + " # 'magmaMinWord',\n", + " # 'magmaWEDist',\n", + " # 'qubitSerfBZ',\n", + " # 'qubitSerfMM',\n", + " # 'dist_m4ri_RW',\n", + " # 'dist_m4ri_CC',\n", + " 'QDistRndMW',\n", + " 'UndetectableErrorStim',\n", + " 'GraphLikeErrorStim',\n", + " 'ColourCodeDistStim',\n", + " 'UndetectableErrorMW',\n", + " 'GraphLikeErrorMW',\n", + " 'ColourCodeDistMW',\n", + " 'decoderDist',\n", + " # 'GurobiDist',\n", + " 'MIPDist',\n", + " # 'pySATDist'\n", + "]\n", + "\n", + "for m in methods:\n", + " obj = ObjectiveFunction(\n", + " code_constructor=cc,\n", + " code_eval_metric=\"distance\",\n", + " dist_method=m\n", + " )\n", + "\n", + " _, est_d = obj.forward(large_code_matrices)\n", + " print(f\"method: {m}, actual distance: {large_known_distance}, estimated distance: {est_d}\")\n", + "\n", + "print()\n", + "print(\"All methods tested!\")\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qec", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.19" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From e6babf874989a8da1b08093cec58f44b1a7e1be1 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Tue, 21 Apr 2026 16:45:09 +0100 Subject: [PATCH 22/36] added configurable timeout to exact distance calculations --- BOplayground.py | 7 +++ bayesian_optimization/objective_function.py | 34 ++++++++++++-- tests/test_dist_eval.ipynb | 51 ++++++++++----------- 3 files changed, 63 insertions(+), 29 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index e35983c..80316b0 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -1001,6 +1001,12 @@ def get_args(): action="store_true", help="use exact code distance rather than LER to evaluate candidate codes", ) + parser.add_argument( + "--distance-timeout", + default=20 * 60, # default 20 minutes + type=int, + help=f"Distance-exact only: number of seconds before exact distance calculation times out. Default {60 * 20}s (20 minutes)", + ) parser.add_argument( "--distance-heuristic", nargs="+", @@ -1087,6 +1093,7 @@ def get_args(): dist_method=dist_method, # reccommend QDistEvol for BB codes dist_params=dist_params, dist_seed=args.seed, + dist_timeout=args.distance_timeout, ) obj_func = Obj_Func.forward pl_to_obj = Obj_Func.pl_to_obj_with_std diff --git a/bayesian_optimization/objective_function.py b/bayesian_optimization/objective_function.py index 5f76b99..dd14f36 100644 --- a/bayesian_optimization/objective_function.py +++ b/bayesian_optimization/objective_function.py @@ -7,6 +7,8 @@ ) import qldpc import codedistance +from multiprocessing import Process, Queue +import queue # ========================= # Utilities @@ -247,6 +249,7 @@ def __init__( dist_method=None, # allows method from https://github.com/m-webster/codeDistancePYPI dist_params={}, dist_seed=None, + dist_timeout=60 * 20, ): self.code_constructor = code_constructor self.n = int(code_constructor.n) @@ -257,6 +260,7 @@ def __init__( self.dist_method = dist_method self.dist_params = dist_params self.dist_seed = dist_seed + self.dist_timeout = dist_timeout # Converters # TODO rename references to pl, as could be evaluating distance instead @@ -355,10 +359,13 @@ def psuedo_t(self, css): t_hat = self.pl_t_converter.pl_to_t(k=None, pl=pL) return float(t_hat), float(pL) + def _distance_worker(self, css: CSSCode, q: Queue): + d = self.distance(css) + q.put(d) + def distance(self, css: CSSCode) -> int: code = qldpc.codes.CSSCode(css.hx, css.hz) - distance = code.get_distance() # this is expensive, TODO add some timeout mechanic to prevent hangs and do some benchmarking - + distance = code.get_distance() if distance <= 0: distance = 1 @@ -390,7 +397,28 @@ def forward(self, x): return float(F), float(pL_total) elif self.code_eval_metric == "distance": if self.dist_method is None: # exact distance calculation - d = self.distance(css) + # spawn separate process that can be timed out + q = Queue() + p = Process(target=self._distance_worker, args=(css, q)) + + p.start() + p.join(self.dist_timeout) + + if p.is_alive(): + p.terminate() + p.join() + raise TimeoutError( + f"Distance evaluation has timed out for code size n={css.n} " + f"after running for {self.dist_timeout // 60} minutes. Consider " + "using a distance approximation method instead with the option" + "'--distance-heuristic', or increase the timeout time with '--distance-timeout'." + ) + + try: + d = q.get(block=False) + except queue.Empty: + raise RuntimeError("Process died without returning a distance") + else: # distance heuristic d = self.approximate_distance(css) t = (d - 1) / 2 # correctable errors diff --git a/tests/test_dist_eval.ipynb b/tests/test_dist_eval.ipynb index 9de51eb..0b65941 100644 --- a/tests/test_dist_eval.ipynb +++ b/tests/test_dist_eval.ipynb @@ -46,15 +46,16 @@ "data_point = np.random.normal(mean, scale, 1)[0]\n", "transformed_dp = normalizer.transform(data_point).item()\n", "print(f\"{data_point} -> {transformed_dp}\")\n", - "assert transformed_dp > -3 and transformed_dp < 3 # standard normal stddev is 1, 99% of the time should be within mean (0) += 3*sd\n", + "assert (\n", + " transformed_dp > -3 and transformed_dp < 3\n", + ") # standard normal stddev is 1, 99% of the time should be within mean (0) += 3*sd\n", "\n", "inverse_transformed_dp = normalizer.inverse_transform(transformed_dp).item()\n", "print(f\"{transformed_dp} -> {inverse_transformed_dp}\")\n", "print(inverse_transformed_dp - data_point)\n", "assert abs(inverse_transformed_dp - data_point) < e\n", "\n", - "print(\"Normalizer tests passed!\")\n", - "\n" + "print(\"Normalizer tests passed!\")" ] }, { @@ -67,9 +68,7 @@ "# test changes in bo/bo\n", "from bayesian_optimization.bo import BO_on_QEC\n", "\n", - "bo = BO_on_QEC(\n", - " code_eval_metric='distance'\n", - ")\n", + "# bo = BO_on_QEC(code_eval_metric=\"distance\")\n", "\n", "# nothing really to test here" ] @@ -87,6 +86,8 @@ "import qldpc\n", "\n", "code_class = \"bb\"\n", + "\n", + "\n", "class DummyCodeConstructor:\n", " def __init__(self):\n", " self.n = 1\n", @@ -96,14 +97,12 @@ "\n", " def construct(self, matrices):\n", " return CSSCode(matrices[0], matrices[1])\n", - " \n", + "\n", + "\n", "cc = DummyCodeConstructor()\n", "\n", "# exact distance\n", - "obj = ObjectiveFunction(\n", - " code_constructor=cc,\n", - " code_eval_metric=\"distance\"\n", - ")\n", + "obj = ObjectiveFunction(code_constructor=cc, code_eval_metric=\"distance\")\n", "\n", "small_code = qldpc.codes.SteaneCode()\n", "small_code_matrices = np.array([small_code.matrix_x, small_code.matrix_z])\n", @@ -123,7 +122,7 @@ "large_known_distance = L\n", "# commented out methods have issues (require license, local binaries missing etc.)\n", "methods = [\n", - " 'QDistEvol',\n", + " \"QDistEvol\",\n", " # 'magmaMinWeight',\n", " # 'magmaMinWord',\n", " # 'magmaWEDist',\n", @@ -131,31 +130,31 @@ " # 'qubitSerfMM',\n", " # 'dist_m4ri_RW',\n", " # 'dist_m4ri_CC',\n", - " 'QDistRndMW',\n", - " 'UndetectableErrorStim',\n", - " 'GraphLikeErrorStim',\n", - " 'ColourCodeDistStim',\n", - " 'UndetectableErrorMW',\n", - " 'GraphLikeErrorMW',\n", - " 'ColourCodeDistMW',\n", - " 'decoderDist',\n", + " \"QDistRndMW\",\n", + " \"UndetectableErrorStim\",\n", + " \"GraphLikeErrorStim\",\n", + " \"ColourCodeDistStim\",\n", + " \"UndetectableErrorMW\",\n", + " \"GraphLikeErrorMW\",\n", + " \"ColourCodeDistMW\",\n", + " \"decoderDist\",\n", " # 'GurobiDist',\n", - " 'MIPDist',\n", + " \"MIPDist\",\n", " # 'pySATDist'\n", "]\n", "\n", "for m in methods:\n", " obj = ObjectiveFunction(\n", - " code_constructor=cc,\n", - " code_eval_metric=\"distance\",\n", - " dist_method=m\n", + " code_constructor=cc, code_eval_metric=\"distance\", dist_method=m\n", " )\n", "\n", " _, est_d = obj.forward(large_code_matrices)\n", - " print(f\"method: {m}, actual distance: {large_known_distance}, estimated distance: {est_d}\")\n", + " print(\n", + " f\"method: {m}, actual distance: {large_known_distance}, estimated distance: {est_d}\"\n", + " )\n", "\n", "print()\n", - "print(\"All methods tested!\")\n" + "print(\"All methods tested!\")" ] } ], From fbd40be108b84a6b5f857b7303421fbf17b0854b Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Wed, 22 Apr 2026 14:56:33 +0100 Subject: [PATCH 23/36] fixed tests, bug fixes, and arg validation --- BOplayground.py | 24 +++++++++++++++-- tests/test_dist_eval.ipynb | 7 ++--- tests/test_exact_dist_eval.py | 51 +++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 tests/test_exact_dist_eval.py diff --git a/BOplayground.py b/BOplayground.py index 80316b0..6015338 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -1021,7 +1021,7 @@ def get_args(): help="index of the dataset of starting codes", ) parser.add_argument( - "--lam", default=1, type=int, help="lambda parameter of the objective function" + "--lam", default=1.0, type=float, help="lambda parameter of the objective function" ) parser.add_argument( "--polynomial-size", @@ -1044,7 +1044,27 @@ def get_args(): help="GB codes only: bitmask of irreducible factors of (x^l - 1) that make up g(x)", ) - return parser.parse_args() + args = parser.parse_args() + + if args.distance_exact and args.distance_heuristic: + raise ValueError("Cannot use both --distance-exact and --distance-heuristic") + + if args.distance_timeout and not args.distance_exact: + raise ValueError(f"--distance-timeout must be used alongside --distance-exact") + + if args.polynomial_size and args.code_class not in ['bb', 'gb']: + raise ValueError(f"--polynomial-size can only be used with bb or gb codes, not {args.code_class}") + + if args.density and args.code_class != 'gb': + raise ValueError(f"--density can only be used with gb codes, not {args.code_class}") + + if args.desired_k and args.code_class != 'gb': + raise ValueError(f"--desired-k can only be used with gb codes, not {args.code_class}") + + if args.gx_mask and args.code_class != 'gb': + raise ValueError(f"--gx-mask can only be used with gb codes, not {args.code_class}") + + return args if __name__ == "__main__": diff --git a/tests/test_dist_eval.ipynb b/tests/test_dist_eval.ipynb index 0b65941..13f2cb3 100644 --- a/tests/test_dist_eval.ipynb +++ b/tests/test_dist_eval.ipynb @@ -81,7 +81,8 @@ "outputs": [], "source": [ "# test changes in bo/objective function\n", - "from bayesian_optimization.objective_function import ObjectiveFunction\n", + "# NO LONGER WORKS DUE TO TIMEOUT: see test_exact_dist_eval.py\n", + "\"\"\"from bayesian_optimization.objective_function import ObjectiveFunction\n", "from code_construction.code_construction import CSSCode\n", "import qldpc\n", "\n", @@ -102,7 +103,7 @@ "cc = DummyCodeConstructor()\n", "\n", "# exact distance\n", - "obj = ObjectiveFunction(code_constructor=cc, code_eval_metric=\"distance\")\n", + "obj = ObjectiveFunction(code_constructor=cc, code_eval_metric=\"distance\", dist_timeout=100)\n", "\n", "small_code = qldpc.codes.SteaneCode()\n", "small_code_matrices = np.array([small_code.matrix_x, small_code.matrix_z])\n", @@ -154,7 +155,7 @@ " )\n", "\n", "print()\n", - "print(\"All methods tested!\")" + "print(\"All methods tested!\")\"\"\"" ] } ], diff --git a/tests/test_exact_dist_eval.py b/tests/test_exact_dist_eval.py new file mode 100644 index 0000000..53706b5 --- /dev/null +++ b/tests/test_exact_dist_eval.py @@ -0,0 +1,51 @@ +# multiprocessing is used for the timeout of exact distance evaluation, and it doesn't work well with notebooks + +import numpy as np +import qldpc + + +import sys +from pathlib import Path + +# hack to access classes outside of test directory +current_file_path = Path(__file__).resolve() +path_root = current_file_path.parent.parent + +if str(path_root) not in sys.path: + sys.path.insert(0, str(path_root)) + +from bayesian_optimization.objective_function import ObjectiveFunction +from code_construction.code_construction import CSSCode + + +# ----------------------------------------------------------------------- + +class DummyCodeConstructor: + def __init__(self): + self.n = 1 + self.k = 1 + + def construct(self, matrices): + return CSSCode(matrices[0], matrices[1]) + + +if __name__ == '__main__': + + print("Initializing...") + cc = DummyCodeConstructor() + obj = ObjectiveFunction( + code_constructor=cc, + code_eval_metric="distance", + dist_timeout=100 + ) + + small_code = qldpc.codes.SteaneCode() + small_code_matrices = np.array([small_code.matrix_x, small_code.matrix_z]) + small_known_distance = 3 + + print("Running objective function...") + _, d = obj.forward(small_code_matrices) + + print(f"Calculated distance: {d}") + assert d == small_known_distance + print("Exact distance test passed!") \ No newline at end of file From 1875992310ffbbf8794035b5faee7c4d20787f29 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Mon, 27 Apr 2026 19:07:04 +0100 Subject: [PATCH 24/36] arg bug fix, small test change, formatting --- BOplayground.py | 57 +++++++++++++++++++++++------------ tests/test_dist_eval.ipynb | 9 +++--- tests/test_exact_dist_eval.py | 12 +++----- 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index 6015338..c2e0ec0 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -1021,7 +1021,10 @@ def get_args(): help="index of the dataset of starting codes", ) parser.add_argument( - "--lam", default=1.0, type=float, help="lambda parameter of the objective function" + "--lam", + default=1.0, + type=float, + help="lambda parameter of the objective function", ) parser.add_argument( "--polynomial-size", @@ -1048,21 +1051,29 @@ def get_args(): if args.distance_exact and args.distance_heuristic: raise ValueError("Cannot use both --distance-exact and --distance-heuristic") - + if args.distance_timeout and not args.distance_exact: raise ValueError(f"--distance-timeout must be used alongside --distance-exact") - - if args.polynomial_size and args.code_class not in ['bb', 'gb']: - raise ValueError(f"--polynomial-size can only be used with bb or gb codes, not {args.code_class}") - - if args.density and args.code_class != 'gb': - raise ValueError(f"--density can only be used with gb codes, not {args.code_class}") - - if args.desired_k and args.code_class != 'gb': - raise ValueError(f"--desired-k can only be used with gb codes, not {args.code_class}") - - if args.gx_mask and args.code_class != 'gb': - raise ValueError(f"--gx-mask can only be used with gb codes, not {args.code_class}") + + if args.polynomial_size and args.code_class not in ["bb", "gb"]: + raise ValueError( + f"--polynomial-size can only be used with bb or gb codes, not {args.code_class}" + ) + + if args.density and args.code_class != "gb": + raise ValueError( + f"--density can only be used with gb codes, not {args.code_class}" + ) + + if args.desired_k and args.code_class != "gb": + raise ValueError( + f"--desired-k can only be used with gb codes, not {args.code_class}" + ) + + if args.gx_mask and args.code_class != "gb": + raise ValueError( + f"--gx-mask can only be used with gb codes, not {args.code_class}" + ) return args @@ -1077,14 +1088,20 @@ def get_args(): else: code_eval_metric = "LER" + dist_params = {} + dist_method = None + if args.distance_heuristic: dist_method = args.distance_heuristic[0] - dist_params = ( - {} if not args.distance_heuristic[1:] else args.distance_heuristic[1:] - ) - else: - dist_method = None - dist_params = {} + + if args.distance_heuristic[1:]: + for arg in args.distance_heuristic[1:]: + k, v = arg.split("=") + try: + v = float(v) + except ValueError: + pass + dist_params[k] = v l = args.polynomial_size[0] if len(args.polynomial_size) > 1: diff --git a/tests/test_dist_eval.ipynb b/tests/test_dist_eval.ipynb index 13f2cb3..6bbe72a 100644 --- a/tests/test_dist_eval.ipynb +++ b/tests/test_dist_eval.ipynb @@ -81,8 +81,7 @@ "outputs": [], "source": [ "# test changes in bo/objective function\n", - "# NO LONGER WORKS DUE TO TIMEOUT: see test_exact_dist_eval.py\n", - "\"\"\"from bayesian_optimization.objective_function import ObjectiveFunction\n", + "from bayesian_optimization.objective_function import ObjectiveFunction\n", "from code_construction.code_construction import CSSCode\n", "import qldpc\n", "\n", @@ -102,6 +101,8 @@ "\n", "cc = DummyCodeConstructor()\n", "\n", + "# NO LONGER WORKS DUE TO TIMEOUT: see test_exact_dist_eval.py\n", + "\"\"\"\n", "# exact distance\n", "obj = ObjectiveFunction(code_constructor=cc, code_eval_metric=\"distance\", dist_timeout=100)\n", "\n", @@ -113,7 +114,7 @@ "\n", "print(\"Exact distance test passed!\")\n", "print()\n", - "\n", + "\"\"\"\n", "\n", "# distance estimation with various methods\n", "L = 10\n", @@ -155,7 +156,7 @@ " )\n", "\n", "print()\n", - "print(\"All methods tested!\")\"\"\"" + "print(\"All methods tested!\")" ] } ], diff --git a/tests/test_exact_dist_eval.py b/tests/test_exact_dist_eval.py index 53706b5..5af0812 100644 --- a/tests/test_exact_dist_eval.py +++ b/tests/test_exact_dist_eval.py @@ -20,6 +20,7 @@ # ----------------------------------------------------------------------- + class DummyCodeConstructor: def __init__(self): self.n = 1 @@ -29,14 +30,11 @@ def construct(self, matrices): return CSSCode(matrices[0], matrices[1]) -if __name__ == '__main__': - +if __name__ == "__main__": print("Initializing...") cc = DummyCodeConstructor() obj = ObjectiveFunction( - code_constructor=cc, - code_eval_metric="distance", - dist_timeout=100 + code_constructor=cc, code_eval_metric="distance", dist_timeout=100 ) small_code = qldpc.codes.SteaneCode() @@ -45,7 +43,7 @@ def construct(self, matrices): print("Running objective function...") _, d = obj.forward(small_code_matrices) - + print(f"Calculated distance: {d}") assert d == small_known_distance - print("Exact distance test passed!") \ No newline at end of file + print("Exact distance test passed!") From b72bfb93b5032963f6a46aaae244adecdf9f96cd Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Thu, 30 Apr 2026 20:30:15 +0100 Subject: [PATCH 25/36] small args fix --- BOplayground.py | 13 +++++++------ bayesian_optimization/objective_function.py | 7 +++++-- tests/test_dist_eval.ipynb | 1 - 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index c2e0ec0..f2ac0a0 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -1003,7 +1003,6 @@ def get_args(): ) parser.add_argument( "--distance-timeout", - default=20 * 60, # default 20 minutes type=int, help=f"Distance-exact only: number of seconds before exact distance calculation times out. Default {60 * 20}s (20 minutes)", ) @@ -1028,7 +1027,6 @@ def get_args(): ) parser.add_argument( "--polynomial-size", - default=[12, 6], type=int, nargs="+", help="BB and GB codes only: sizes of polynomials a(x) and b(x)", @@ -1103,11 +1101,14 @@ def get_args(): pass dist_params[k] = v - l = args.polynomial_size[0] - if len(args.polynomial_size) > 1: - g = args.polynomial_size[1] + if not args.polynomial_size: + l, g = 12, 6 # default value else: - g = None + l = args.polynomial_size[0] + if len(args.polynomial_size) > 1: + g = args.polynomial_size[1] + else: + g = None set_all_seeds(args.seed) diff --git a/bayesian_optimization/objective_function.py b/bayesian_optimization/objective_function.py index dd14f36..d70f541 100644 --- a/bayesian_optimization/objective_function.py +++ b/bayesian_optimization/objective_function.py @@ -249,7 +249,7 @@ def __init__( dist_method=None, # allows method from https://github.com/m-webster/codeDistancePYPI dist_params={}, dist_seed=None, - dist_timeout=60 * 20, + dist_timeout=None, ): self.code_constructor = code_constructor self.n = int(code_constructor.n) @@ -260,7 +260,10 @@ def __init__( self.dist_method = dist_method self.dist_params = dist_params self.dist_seed = dist_seed - self.dist_timeout = dist_timeout + if not dist_timeout: + self.dist_timeout = 60 * 20 # default: 20 minutes + else: + self.dist_timeout = dist_timeout # Converters # TODO rename references to pl, as could be evaluating distance instead diff --git a/tests/test_dist_eval.ipynb b/tests/test_dist_eval.ipynb index 6bbe72a..baf804b 100644 --- a/tests/test_dist_eval.ipynb +++ b/tests/test_dist_eval.ipynb @@ -87,7 +87,6 @@ "\n", "code_class = \"bb\"\n", "\n", - "\n", "class DummyCodeConstructor:\n", " def __init__(self):\n", " self.n = 1\n", From ab07ca40c2ec600b21899a3f1698dc102b03faa3 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Fri, 1 May 2026 15:00:31 +0100 Subject: [PATCH 26/36] small optimisations --- BOplayground.py | 17 ++++++++------ code_construction/code_construction.py | 31 +++++++++++++------------- evaluation/decoder_based_evaluation.py | 18 +++++++-------- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index f2ac0a0..291d438 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -1081,6 +1081,9 @@ def get_args(): args = get_args() + import time + start_time = time.perf_counter() + if args.distance_exact or args.distance_heuristic: code_eval_metric = "distance" else: @@ -1193,12 +1196,9 @@ def get_args(): y_init = data["y"] pl_init = data["pl"] - X_init = torch.tensor(X_init, dtype=torch.float64) - X_init.to(DEVICE) # think this line should be X_init = X_init.to(DEVICE) - y_init = torch.tensor(y_init, dtype=torch.float32) - y_init.to(DEVICE) - pl_init = torch.tensor(pl_init, dtype=torch.float32) - pl_init.to(DEVICE) + X_init = torch.tensor(X_init, dtype=torch.float64).to(DEVICE) + y_init = torch.tensor(y_init, dtype=torch.float32).to(DEVICE) + pl_init = torch.tensor(pl_init, dtype=torch.float32).to(DEVICE) # get gp model model = get_model_( X_init, @@ -1272,7 +1272,7 @@ def code_validator(candidate_np): ) # assemble BO - bo_iterations = 50 + bo_iterations = 10 bo = BO_on_QEC( gp=model, gp_trainer=trainer, @@ -1289,6 +1289,8 @@ def code_validator(candidate_np): code_eval_metric=code_eval_metric, ) best_x, best_y, evaluation_history = bo.run() + end_time = time.perf_counter() + print(f"Time taken: {end_time - start_time:.2f} seconds") # The best-so-far results of bo (including initial points) flat = [v for row in evaluation_history for v in row] @@ -1297,6 +1299,7 @@ def code_validator(candidate_np): results_file = init_data_file.replace("BO_initial_points", "BO_results") + with open(results_file, "wb") as f: results = {"best_x": best_x, "best_y": best_y, "evaluation_history": flat} pickle.dump(results, f) diff --git a/code_construction/code_construction.py b/code_construction/code_construction.py index 2dd702c..c9ed0ee 100644 --- a/code_construction/code_construction.py +++ b/code_construction/code_construction.py @@ -466,8 +466,9 @@ def gx_mask_to_bin(gx_mask, fs, l) -> int: return gx_bin - def _build_circulant_matrix(self, row) -> np.ndarray: - length = len(row) + def _build_circulant_matrix(self, row, length=None) -> np.ndarray: + if length is None: + length = len(row) col_idx = np.arange(length)[None, :] row_idx = np.arange(length)[:, None] @@ -618,10 +619,10 @@ def corresponding_matrix_BA(self, g_size, A): for i in range(r): for j in range(c): if j == 0: - r_matrix = self.corresponding_matrix_Ba(g_size, A[i][j]) + r_matrix = self._build_circulant_matrix(A[i][j], g_size) else: r_matrix = np.hstack( - (r_matrix, self.corresponding_matrix_Ba(g_size, A[i][j])) + (r_matrix, self._build_circulant_matrix(A[i][j], g_size)) ) if i == 0: B_A = r_matrix @@ -629,17 +630,17 @@ def corresponding_matrix_BA(self, g_size, A): B_A = np.vstack((B_A, r_matrix)) return B_A - def corresponding_matrix_Ba(self, g_size, a): - B_a = np.zeros((g_size, g_size), dtype=int) - for i in range(g_size): - B_a = (B_a + a[i] * self.g_power(g_size, i)) % 2 - return B_a - - def g_power(self, g_size, p): - g_p = np.zeros((g_size, g_size), dtype=int) - for i in range(g_size): - g_p[i][int((i + p) % g_size)] = 1 - return g_p + # def corresponding_matrix_Ba(self, g_size, a): + # B_a = np.zeros((g_size, g_size), dtype=int) + # for i in range(g_size): + # B_a = (B_a + a[i] * self.g_power(g_size, i)) % 2 + # return B_a + + # def g_power(self, g_size, p): + # g_p = np.zeros((g_size, g_size), dtype=int) + # for i in range(g_size): + # g_p[i][int((i + p) % g_size)] = 1 + # return g_p def arbitrary_bivariate_bicycle_code(self, parameters): # a=[a_0,a_1,...,a_{l+g-2}] is a rep of coefficient of A=a_0*I + a_1*x + a_2*x^2 +...+ a_{l-1}*x^{l-1} + a_l*y + a_{l+1}*y^2 +...+ a_{l+g-2}*y^{g-1} diff --git a/evaluation/decoder_based_evaluation.py b/evaluation/decoder_based_evaluation.py index 4290ca0..c5ccb6b 100644 --- a/evaluation/decoder_based_evaluation.py +++ b/evaluation/decoder_based_evaluation.py @@ -92,9 +92,9 @@ def Get_distribution_dij(G, trails=1000): class CSS_Evaluator: def __init__(self, hx, hz): # print(f'initializing') - self.hx = hx - self.hz = hz - qcode = css_code(self.hx, self.hz) + qcode = css_code(hx, hz) + self.hx = qcode.hx + self.hz = qcode.hz self.lx = qcode.lx self.lz = qcode.lz # logical operators self.k = qcode.K @@ -390,26 +390,26 @@ def P_L_given_n_e(self, n_e, trail=100): def Single_run(self, error_x, error_z): if error_z.any(): - synd_z = (self.hx @ error_z) % 2 + synd_z = (self.hx.dot(error_z)) % 2 rec_z = self.bpd_z.decode(synd_z) residual_z = (error_z + rec_z) % 2 else: residual_z = error_z.copy() if error_x.any(): - synd_x = (self.hz @ error_x) % 2 + synd_x = (self.hz.dot(error_x)) % 2 rec_x = self.bpd_x.decode(synd_x) residual_x = (error_x + rec_x) % 2 else: residual_x = error_x.copy() - sz_cleared = ((self.hx @ residual_z) % 2 == 0).all() - sx_cleared = ((self.hz @ residual_x) % 2 == 0).all() + sz_cleared = ((self.hx.dot(residual_z)) % 2 == 0).all() + sx_cleared = ((self.hz.dot(residual_x)) % 2 == 0).all() if not (sz_cleared and sx_cleared): return 1 - logical_x_flip = ((self.lx @ residual_x) % 2).any() - logical_z_flip = ((self.lz @ residual_z) % 2).any() + logical_x_flip = ((self.lx.dot(residual_x)) % 2).any() + logical_z_flip = ((self.lz.dot(residual_z)) % 2).any() if logical_x_flip or logical_z_flip: return 1 From 5e03b170f632810ea27be9a2abd3ca97c59e1096 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Mon, 4 May 2026 17:16:20 +0100 Subject: [PATCH 27/36] changed internal rep of all parameters to be binary arrays, and changed hill climbing/random gb generation to focus on density rather than divisibility --- BOplayground.py | 216 ++++++++++++++++--------- code_construction/code_construction.py | 35 ++-- 2 files changed, 159 insertions(+), 92 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index 291d438..23c2c1c 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -7,6 +7,7 @@ from torch.nn.utils import clip_grad_norm_ from torch.distributions.normal import Normal import sympy +import scipy from code_construction.code_construction import CodeConstructor from bayesian_optimization.objective_function import ObjectiveFunction @@ -48,7 +49,7 @@ def __init__( self.target_row_weight = target_row_weight match method: case "gb": - self.mutate = self.swap_factors + self.mutate = self.mutate_gb case "bb": self.mutate = self.flip_neighbours case _: @@ -64,56 +65,125 @@ def flip_neighbours(self, x: torch.Tensor) -> torch.Tensor: n[i] = 1.0 - n[i] neigh_list.append(n) return torch.stack(neigh_list, dim=0) # [d, d] - - def swap_factors(self, x: torch.Tensor) -> torch.Tensor: + + def _bin_arr_to_int(self, bin_arr): + return sum([bit << i for i, bit in enumerate(bin_arr)]) + + def mutate_gb(self, x: torch.Tensor) -> torch.Tensor: + # existing p(x) is divided by g(x) + # flipping one 0->1 and one 1->0 to keep density, same as: + # p(x) + x^i + x^j + # If this must be divisible by g(x), then so must be the delta (x^i + x^j) = x^i(1 + x^|j-i|) + # let d = |j-i| x = x.detach() - gx_mask = int(x[0].item()) - qa = int(x[1].item()) - qb = int(x[2].item()) - fs = [int(f.item()) for f in x[3:]] + a = x[1] + a_len = len(a) + b = x[2] + b_len = len(b) + int_fs = map(self._bin_arr_to_int, x[3:]) + int_gx_mask = self._bin_arr_to_int(x[0]) + gx_bin = CodeConstructor.gx_mask_to_bin(int_gx_mask, int_fs, self.l) - gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, self.l) + valid_dists = [] - max_bound = self.l - (gx_bin.bit_length() - 1) + for d in range(1, self.l): + delta = 1 + (1 << d) + if CodeConstructor.poly_mod_f2(delta, gx_bin) == 0: + valid_dists.append(d) neigh_list = [] - for i in range(max_bound): - for j in range(i, max_bound): - if i == j: - new_qa = qa ^ (1 << i) # flips one factor of qa - new_qb = qb ^ (1 << i) - else: - new_qa = qa ^ (1 << i) ^ (1 << j) # flips two factors of qa - new_qb = qb ^ (1 << i) ^ (1 << j) - - a_weight = CodeConstructor.multiply_polynomials_mod_l( - gx_bin, new_qa, self.l - ).bit_count() - - b_weight = CodeConstructor.multiply_polynomials_mod_l( - gx_bin, new_qb, self.l - ).bit_count() - - if ( - a_weight == self.target_row_weight - ): # checks if new a(x) preserves LDPC density - n1 = x.clone() - n1[1] = new_qa - neigh_list.append(n1) - - if ( - b_weight == self.target_row_weight - ): # checks if new b(x) preserves LDPC density - n2 = x.clone() - n2[2] = new_qb - neigh_list.append(n2) - + + a_ones = (a == 1).nonzero().squeeze().tolist() + for one_pos in a_ones: + for d in valid_dists: + if a[(one_pos + d) % a_len].item() == 0: + new_a = a.clone() + new_a[one_pos] = 0 + new_a[(one_pos + d) % a_len] = 1 + new = x.clone() + new[1] = new_a + neigh_list.append(new) + if a[(one_pos - d) % a_len].item() == 0 and ((one_pos - d) % a_len) != ((one_pos + d) % a_len): + new_a = a.clone() + new_a[one_pos] = 0 + new_a[(one_pos - d) % a_len] = 1 + new = x.clone() + new[1] = new_a + neigh_list.append(new) + + b_ones = (b == 1).nonzero().squeeze().tolist() + for one_pos in b_ones: + for d in valid_dists: + if b[(one_pos + d) % b_len].item() == 0: + new_b = b.clone() + new_b[one_pos] = 0 + new_b[(one_pos + d) % b_len] = 1 + new = x.clone() + new[1] = new_b + neigh_list.append(new) + if b[(one_pos - d) % b_len].item() == 0 and ((one_pos - d) % b_len) != ((one_pos + d) % b_len): + new_b = b.clone() + new_b[one_pos] = 0 + new_b[(one_pos - d) % b_len] = 1 + new = x.clone() + new[1] = new_b + neigh_list.append(new) + if not neigh_list: return torch.empty((0, len(x)), dtype=x.dtype, device=x.device) return torch.stack(neigh_list, dim=0) + # def swap_factors(self, x: torch.Tensor) -> torch.Tensor: + # x = x.detach() + + # gx_mask = int(x[0].item()) + # qa = int(x[1].item()) + # qb = int(x[2].item()) + # fs = [int(f.item()) for f in x[3:]] + + # gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, self.l) + + # max_bound = self.l - (gx_bin.bit_length() - 1) + + # neigh_list = [] + # for i in range(max_bound): + # for j in range(i, max_bound): + # if i == j: + # new_qa = qa ^ (1 << i) # flips one factor of qa + # new_qb = qb ^ (1 << i) + # else: + # new_qa = qa ^ (1 << i) ^ (1 << j) # flips two factors of qa + # new_qb = qb ^ (1 << i) ^ (1 << j) + + # a_weight = CodeConstructor.multiply_polynomials_mod_l( + # gx_bin, new_qa, self.l + # ).bit_count() + + # b_weight = CodeConstructor.multiply_polynomials_mod_l( + # gx_bin, new_qb, self.l + # ).bit_count() + + # if ( + # a_weight == self.target_row_weight + # ): # checks if new a(x) preserves LDPC density + # n1 = x.clone() + # n1[1] = new_qa + # neigh_list.append(n1) + + # if ( + # b_weight == self.target_row_weight + # ): # checks if new b(x) preserves LDPC density + # n2 = x.clone() + # n2[2] = new_qb + # neigh_list.append(n2) + + # if not neigh_list: + # return torch.empty((0, len(x)), dtype=x.dtype, device=x.device) + + # return torch.stack(neigh_list, dim=0) + @torch.no_grad() def __call__(self, gp) -> torch.Tensor: X0_np = self.gnp(self.next_points_num) # [n, d], 0/1 @@ -903,7 +973,7 @@ def _get_possible_gx_bitmasks(self, factors): return valid_bitmasks - def set_gx_mask(self): + def set_gx_mask(self): # gx_mask is input as an int and later converted to a binary array l = self.code_constructor.para_dict["l"] self.factors = self._get_irreducible_factors(l) if self.gx_mask is not None: @@ -916,51 +986,45 @@ def set_gx_mask(self): self.gx_mask = random.choice(possible_gx_masks) print(f"Randomly selected g(x) bitmask = {self.gx_mask}") + def _generate_candidate_poly(self, max_index): + indices = np.random.choice(max_index, self.density, replace=False) + + res = 0 + for idx in indices: + res |= 1 << idx + + return res + + + def _int_to_bin_list(bin_int): + return [int(bit) for bit in bin(bin_int)[2:]][::-1] # least -> most significant bit + + def get_new_gb_vector(self, number): results = [] l = self.code_constructor.para_dict["l"] gx_bin = CodeConstructor.gx_mask_to_bin(self.gx_mask, self.factors, l) - - max_bound = 2 ** (l - (gx_bin.bit_length() - 1)) + max_bound = l - (gx_bin.bit_length() - 1) while number > 0: - batch_guesses = np.random.randint(1, max_bound, size=10_000) - qa_found = False - - for qa in batch_guesses: - qa = qa.item() - - # qa = np.random.randint(1, max_bound) - weight_a = CodeConstructor.multiply_polynomials_mod_l( - gx_bin, qa, l - ).bit_count() - # if weight_a != self.density: - # continue - if weight_a == self.density: - qa_found = True - break - - if qa_found: - qb_found = False - while not qb_found: - batch_guesses = np.random.randint(1, max_bound, size=10_000) - - for qb in batch_guesses: - qb = qb.item() - weight_b = CodeConstructor.multiply_polynomials_mod_l( - gx_bin, qb, l - ).bit_count() - if weight_b == self.density: - qb_found = True - break - - parameters = [self.gx_mask, qa, qb] + self.factors - + cand_a = self._generate_candidate_poly(max_bound) + if CodeConstructor.poly_mod_f2(cand_a, gx_bin) == 0: # gx_bin divides cand_a + + found_b = False + while not found_b: + cand_b = self._generate_candidate_poly(max_bound) + if CodeConstructor.poly_mod_f2(cand_b, gx_bin) == 0: + found_b = True + + parameters = [ + self._int_to_bin_list(self.gx_mask), + self._int_to_bin_list(cand_a), + self._int_to_bin_list(cand_b) + ] + list(map(self._int_to_bin_list, self.factors)) results.append(parameters) number -= 1 - return np.array(results) def get_new_bb_vector(self, number): results = [] diff --git a/code_construction/code_construction.py b/code_construction/code_construction.py index c9ed0ee..b1c5f00 100644 --- a/code_construction/code_construction.py +++ b/code_construction/code_construction.py @@ -465,6 +465,17 @@ def gx_mask_to_bin(gx_mask, fs, l) -> int: gx_bin = CodeConstructor.multiply_polynomials_mod_l(gx_bin, fs[i], l) return gx_bin + + @staticmethod + def poly_mod_f2(p, g): + deg_g = g.bit_length() - 1 + deg_p = p.bit_length() - 1 + while deg_p >= deg_g: # continuously subtract multiples of g(x) from p(x) + shift = deg_p - deg_g + p ^= (g << shift) + deg_p = p.bit_length() - 1 + + return p # p is remainder def _build_circulant_matrix(self, row, length=None) -> np.ndarray: if length is None: @@ -482,26 +493,18 @@ def _build_bicycle_css_code(self, A, B) -> CSSCode: def generalised_bicycle_code(self, parameters) -> CSSCode: """ - parameters = [gx_mask, qa, qb, f1, ..., fn] - gx_mask (int): binary mask of which irreducable factors of (x^l - 1) make up q(x) - qa (int): binary representation of polynomial q_a(x) - qb (int): binary representation of polynomial q_b(x) - fk (int): binary representation of the irreducable factors of (x^l - 1) + parameters = [gx_mask, a, b, f1, ..., fn] + gx_mask (list[binary]): binary mask of which irreducable factors of (x^l - 1) make up g(x) + a (list[binary]): binary representation of polynomial a(x) + b (list[binary]): binary representation of polynomial b(x) + fk (list[binary]): binary representation of the irreducable factors of (x^l - 1) """ l = self.para_dict["l"] - gx_mask, qa, qb = [int(x) for x in parameters[:3]] - fs = [int(x) for x in parameters[3:]] - - # compute g(x) polynomial - gx_bin = self.gx_mask_to_bin(gx_mask, fs, l) - - # calculate a(x) = g(x) * q_a(x), b(x) = g(x) * q_b(x) - a_int = CodeConstructor.multiply_polynomials_mod_l(gx_bin, qa, l) - b_int = CodeConstructor.multiply_polynomials_mod_l(gx_bin, qb, l) + _, a, b = parameters[1:3] - a_array = np.array([int(bit) for bit in bin(a_int)[2:].zfill(l)[::-1]]) - b_array = np.array([int(bit) for bit in bin(b_int)[2:].zfill(l)[::-1]]) + a_array = np.array(a) + b_array = np.array(b) A = self._build_circulant_matrix(a_array) B = self._build_circulant_matrix(b_array) From 90c8ced039ecad0afe75c16a7ce988dbe7a55232 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Tue, 5 May 2026 13:18:16 +0100 Subject: [PATCH 28/36] fixed issues with int to binary vector migration, updated tests in test_gb_codes --- BOplayground.py | 72 +++++---- code_construction/code_construction.py | 6 +- tests/test_dist_eval.ipynb | 1 + tests/test_gb_codes.ipynb | 203 +++++++++++++++---------- 4 files changed, 171 insertions(+), 111 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index 23c2c1c..76fcc4f 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -65,10 +65,11 @@ def flip_neighbours(self, x: torch.Tensor) -> torch.Tensor: n[i] = 1.0 - n[i] neigh_list.append(n) return torch.stack(neigh_list, dim=0) # [d, d] - - def _bin_arr_to_int(self, bin_arr): - return sum([bit << i for i, bit in enumerate(bin_arr)]) - + + @staticmethod + def bin_list_to_int(bin_list): + return sum([int(bit) << i for i, bit in enumerate(bin_list)]) + def mutate_gb(self, x: torch.Tensor) -> torch.Tensor: # existing p(x) is divided by g(x) # flipping one 0->1 and one 1->0 to keep density, same as: @@ -79,10 +80,12 @@ def mutate_gb(self, x: torch.Tensor) -> torch.Tensor: a = x[1] a_len = len(a) + b = x[2] b_len = len(b) - int_fs = map(self._bin_arr_to_int, x[3:]) - int_gx_mask = self._bin_arr_to_int(x[0]) + + int_fs = list(map(HillClimbing.bin_list_to_int, x[3:].tolist())) + int_gx_mask = HillClimbing.bin_list_to_int(x[0].tolist()) gx_bin = CodeConstructor.gx_mask_to_bin(int_gx_mask, int_fs, self.l) valid_dists = [] @@ -93,7 +96,7 @@ def mutate_gb(self, x: torch.Tensor) -> torch.Tensor: valid_dists.append(d) neigh_list = [] - + a_ones = (a == 1).nonzero().squeeze().tolist() for one_pos in a_ones: for d in valid_dists: @@ -104,7 +107,9 @@ def mutate_gb(self, x: torch.Tensor) -> torch.Tensor: new = x.clone() new[1] = new_a neigh_list.append(new) - if a[(one_pos - d) % a_len].item() == 0 and ((one_pos - d) % a_len) != ((one_pos + d) % a_len): + if a[(one_pos - d) % a_len].item() == 0 and ((one_pos - d) % a_len) != ( + (one_pos + d) % a_len + ): new_a = a.clone() new_a[one_pos] = 0 new_a[(one_pos - d) % a_len] = 1 @@ -122,14 +127,16 @@ def mutate_gb(self, x: torch.Tensor) -> torch.Tensor: new = x.clone() new[1] = new_b neigh_list.append(new) - if b[(one_pos - d) % b_len].item() == 0 and ((one_pos - d) % b_len) != ((one_pos + d) % b_len): + if b[(one_pos - d) % b_len].item() == 0 and ((one_pos - d) % b_len) != ( + (one_pos + d) % b_len + ): new_b = b.clone() new_b[one_pos] = 0 new_b[(one_pos - d) % b_len] = 1 new = x.clone() new[1] = new_b neigh_list.append(new) - + if not neigh_list: return torch.empty((0, len(x)), dtype=x.dtype, device=x.device) @@ -973,7 +980,9 @@ def _get_possible_gx_bitmasks(self, factors): return valid_bitmasks - def set_gx_mask(self): # gx_mask is input as an int and later converted to a binary array + def set_gx_mask( + self, + ): # gx_mask is input as an int and later converted to a binary array l = self.code_constructor.para_dict["l"] self.factors = self._get_irreducible_factors(l) if self.gx_mask is not None: @@ -986,19 +995,20 @@ def set_gx_mask(self): # gx_mask is input as an int and later converted to a bi self.gx_mask = random.choice(possible_gx_masks) print(f"Randomly selected g(x) bitmask = {self.gx_mask}") - def _generate_candidate_poly(self, max_index): - indices = np.random.choice(max_index, self.density, replace=False) - + def _generate_candidate_poly(self, max_index) -> int: + indices = np.random.choice(max_index, self.density, replace=False).tolist() + res = 0 for idx in indices: res |= 1 << idx return res - - def _int_to_bin_list(bin_int): - return [int(bit) for bit in bin(bin_int)[2:]][::-1] # least -> most significant bit - + @staticmethod + def int_to_bin_list(bin_int): + return [int(bit) for bit in bin(bin_int)[2:]][ + ::-1 + ] # least -> most significant bit def get_new_gb_vector(self, number): results = [] @@ -1009,22 +1019,32 @@ def get_new_gb_vector(self, number): while number > 0: cand_a = self._generate_candidate_poly(max_bound) - if CodeConstructor.poly_mod_f2(cand_a, gx_bin) == 0: # gx_bin divides cand_a - + if ( + CodeConstructor.poly_mod_f2(cand_a, gx_bin) == 0 + ): # gx_bin divides cand_a found_b = False while not found_b: cand_b = self._generate_candidate_poly(max_bound) if CodeConstructor.poly_mod_f2(cand_b, gx_bin) == 0: found_b = True + padded_arr = np.zeros((3 + len(self.factors), l), dtype=np.uint8) + parameters = [ - self._int_to_bin_list(self.gx_mask), - self._int_to_bin_list(cand_a), - self._int_to_bin_list(cand_b) - ] + list(map(self._int_to_bin_list, self.factors)) - results.append(parameters) + Get_new_points_function.int_to_bin_list(self.gx_mask), + Get_new_points_function.int_to_bin_list(cand_a), + Get_new_points_function.int_to_bin_list(cand_b), + ] + list(map(Get_new_points_function.int_to_bin_list, self.factors)) + + for i, param in enumerate(parameters): + padded_arr[i, : len(param)] = ( + param # zero pad parameter matrix, works as binary is lsb first + ) + + results.append(padded_arr) number -= 1 + return np.array(results) def get_new_bb_vector(self, number): results = [] @@ -1146,6 +1166,7 @@ def get_args(): args = get_args() import time + start_time = time.perf_counter() if args.distance_exact or args.distance_heuristic: @@ -1363,7 +1384,6 @@ def code_validator(candidate_np): results_file = init_data_file.replace("BO_initial_points", "BO_results") - with open(results_file, "wb") as f: results = {"best_x": best_x, "best_y": best_y, "evaluation_history": flat} pickle.dump(results, f) diff --git a/code_construction/code_construction.py b/code_construction/code_construction.py index b1c5f00..d9384ae 100644 --- a/code_construction/code_construction.py +++ b/code_construction/code_construction.py @@ -465,14 +465,14 @@ def gx_mask_to_bin(gx_mask, fs, l) -> int: gx_bin = CodeConstructor.multiply_polynomials_mod_l(gx_bin, fs[i], l) return gx_bin - + @staticmethod def poly_mod_f2(p, g): deg_g = g.bit_length() - 1 deg_p = p.bit_length() - 1 while deg_p >= deg_g: # continuously subtract multiples of g(x) from p(x) shift = deg_p - deg_g - p ^= (g << shift) + p ^= g << shift deg_p = p.bit_length() - 1 return p # p is remainder @@ -501,7 +501,7 @@ def generalised_bicycle_code(self, parameters) -> CSSCode: """ l = self.para_dict["l"] - _, a, b = parameters[1:3] + a, b = parameters[1:3] a_array = np.array(a) b_array = np.array(b) diff --git a/tests/test_dist_eval.ipynb b/tests/test_dist_eval.ipynb index baf804b..6bbe72a 100644 --- a/tests/test_dist_eval.ipynb +++ b/tests/test_dist_eval.ipynb @@ -87,6 +87,7 @@ "\n", "code_class = \"bb\"\n", "\n", + "\n", "class DummyCodeConstructor:\n", " def __init__(self):\n", " self.n = 1\n", diff --git a/tests/test_gb_codes.ipynb b/tests/test_gb_codes.ipynb index edf96a9..d16d4ea 100644 --- a/tests/test_gb_codes.ipynb +++ b/tests/test_gb_codes.ipynb @@ -109,10 +109,39 @@ "metadata": {}, "outputs": [], "source": [ - "# TEST: are randomly generated GB codes valid\n", + "# TEST: test int to binary list and vice versa\n", + "from BOplayground import HillClimbing\n", "from BOplayground import Get_new_points_function\n", "\n", "\n", + "assert HillClimbing.bin_list_to_int([1, 1, 1, 0, 0, 0, 1]) == 71\n", + "\n", + "assert Get_new_points_function.int_to_bin_list(71) == [1, 1, 1, 0, 0, 0, 1]\n", + "\n", + "print(\"Test Passed!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "# TODO: test these\n", + "# _generate_candidate_poly\n", + "# poly_mod_f2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "# TEST: are randomly generated GB codes valid\n", + "\n", "# this is the same as multiply_polynomials_mod_l but without the mod l\n", "def binary_poly_mul(p1, p2):\n", " res = 0\n", @@ -125,40 +154,37 @@ "para_dict = {\"l\": l}\n", "code_class = \"gb\"\n", "target_weight = 3\n", - "desired_k = 12\n", + "desired_k = 4\n", "\n", "cc = CodeConstructor(method=code_class, para_dict=para_dict)\n", "\n", - "\n", "gnp = Get_new_points_function(\n", " method=code_class, code_constructor=cc, density=target_weight, desired_k=desired_k\n", ")\n", "\n", "gnp.set_gx_mask()\n", "\n", - "\n", "x_exp_l_minus_1 = 1 + (1 << l) # x^l - 1 = x^l + 1 in binary ring\n", "\n", "new_points = gnp.get_new_points_function(5)\n", "\n", "for point in new_points:\n", - " gx_mask, qa, qb = [int(x) for x in point[:3]]\n", - " fs = [int(x) for x in point[3:]]\n", + " gx_mask, a, b = point[:3]\n", + " int_gx_mask = HillClimbing.bin_list_to_int(gx_mask)\n", + "\n", + " fs = point[3:]\n", + " int_fs = list(map(HillClimbing.bin_list_to_int, fs))\n", + "\n", " prod_fs = 1\n", - " for f in fs:\n", + " for f in int_fs:\n", " prod_fs = binary_poly_mul(prod_fs, f)\n", "\n", " assert prod_fs == x_exp_l_minus_1\n", "\n", - " gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, l)\n", - " assert (\n", - " CodeConstructor.multiply_polynomials_mod_l(gx_bin, qa, l).bit_count()\n", - " == target_weight\n", - " ) # LHS: a(x)\n", - " assert (\n", - " CodeConstructor.multiply_polynomials_mod_l(gx_bin, qb, l).bit_count()\n", - " == target_weight\n", - " ) # LHD: b(x)\n", + " gx_bin = CodeConstructor.gx_mask_to_bin(int_gx_mask, int_fs, l)\n", + "\n", + " assert sum(a) == target_weight\n", + " assert sum(b) == target_weight\n", "\n", "print(\"Test Passed!\")" ] @@ -166,7 +192,7 @@ { "cell_type": "code", "execution_count": null, - "id": "5", + "id": "7", "metadata": {}, "outputs": [], "source": [ @@ -190,7 +216,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6", + "id": "8", "metadata": {}, "outputs": [], "source": [ @@ -212,57 +238,65 @@ { "cell_type": "code", "execution_count": null, - "id": "7", + "id": "9", "metadata": {}, "outputs": [], "source": [ "# TEST: is the CSS code generated by CodeConstructor correct for a given GB internal representation\n", "import numpy as np\n", "\n", - "a = 23 # 000010111 -> 1 + x + x^2 + x^4\n", - "b = 9 # 000001001 -> 1 + x^3\n", + "a = 23 # 000010111\n", + "b = 9 # 000001001\n", "\n", - "qa = 13 # 1101 -> 1 + x^2 + x^3\n", - "qb = 7 # 111 -> 1 + x + x^2\n", - "gx_mask = 1 # 001, gx_bin=3\n", - "fs = [3, 7, 73] # from l=9 code above\n", - "gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, l)\n", + "a = [1, 1, 1, 0, 1] # -> 1 + x + x^2 + x^4\n", + "b = [1, 0, 0, 1] # -> 1 + x^3\n", + "\n", + "gx_mask = [1] # 001, gx_bin=[1, 1]\n", + "fs = [[1, 1], [1, 1, 1], [1, 0, 0, 1, 0, 0, 1]] # from l=9 code above\n", "\n", - "assert CodeConstructor.multiply_polynomials_mod_l(gx_bin, qa, l) == a\n", - "assert CodeConstructor.multiply_polynomials_mod_l(gx_bin, qb, l) == b\n", + "int_gx_mask = HillClimbing.bin_list_to_int(gx_mask)\n", + "int_fs = list(map(HillClimbing.bin_list_to_int, fs))\n", "\n", - "x = [gx_mask, qa, qb] + fs\n", + "gx_bin = CodeConstructor.gx_mask_to_bin(int_gx_mask, int_fs, l)\n", "\n", - "a_lsb_first = [0, 0, 0, 0, 1, 0, 1, 1, 1][\n", - " ::-1\n", - "] # bit order is flipped for A&B matrices (least significant bit on the left)\n", - "b_lsb_first = [0, 0, 0, 0, 0, 1, 0, 0, 1][::-1]\n", + "params = [gx_mask, a, b] + fs\n", + "\n", + "# from Get_new_point_function.get_new_gb_vector -----\n", + "x = np.zeros((3 + len(fs), l), dtype=np.uint8)\n", + "for i, param in enumerate(params):\n", + " x[i, : len(param)] = param\n", + "# ---------------------------------------------------\n", + "\n", + "a_padded = a + ([0] * (l - len(a)))\n", + "print(a_padded)\n", + "b_padded = b + ([0] * (l - len(b)))\n", + "print(b_padded)\n", "\n", "expected_A = np.array(\n", " [\n", - " a_lsb_first,\n", - " np.roll(a_lsb_first, 1),\n", - " np.roll(a_lsb_first, 2),\n", - " np.roll(a_lsb_first, 3),\n", - " np.roll(a_lsb_first, 4),\n", - " np.roll(a_lsb_first, 5),\n", - " np.roll(a_lsb_first, 6),\n", - " np.roll(a_lsb_first, 7),\n", - " np.roll(a_lsb_first, 8),\n", + " a_padded,\n", + " np.roll(a_padded, 1),\n", + " np.roll(a_padded, 2),\n", + " np.roll(a_padded, 3),\n", + " np.roll(a_padded, 4),\n", + " np.roll(a_padded, 5),\n", + " np.roll(a_padded, 6),\n", + " np.roll(a_padded, 7),\n", + " np.roll(a_padded, 8),\n", " ]\n", ")\n", "\n", "expected_B = np.array(\n", " [\n", - " b_lsb_first,\n", - " np.roll(b_lsb_first, 1),\n", - " np.roll(b_lsb_first, 2),\n", - " np.roll(b_lsb_first, 3),\n", - " np.roll(b_lsb_first, 4),\n", - " np.roll(b_lsb_first, 5),\n", - " np.roll(b_lsb_first, 6),\n", - " np.roll(b_lsb_first, 7),\n", - " np.roll(b_lsb_first, 8),\n", + " b_padded,\n", + " np.roll(b_padded, 1),\n", + " np.roll(b_padded, 2),\n", + " np.roll(b_padded, 3),\n", + " np.roll(b_padded, 4),\n", + " np.roll(b_padded, 5),\n", + " np.roll(b_padded, 6),\n", + " np.roll(b_padded, 7),\n", + " np.roll(b_padded, 8),\n", " ]\n", ")\n", "\n", @@ -284,35 +318,38 @@ { "cell_type": "code", "execution_count": null, - "id": "8", + "id": "10", "metadata": {}, "outputs": [], "source": [ "# TEST: are generated neighbours of a GB code valid, do they have the same g(x) and have one q the same, and one different\n", - "from BOplayground import HillClimbing\n", "import torch\n", "\n", - "# gx_mask = None\n", - "# qa = None\n", - "# qb = None\n", + "a = [1, 0, 1] # 1 + x^2\n", + "b = [1, 0, 0, 1]\n", "\n", - "# l = None\n", + "gx_mask = [1, 0, 0]\n", "\n", - "# fs = []\n", - "qa = 3 # 11 -> 1 + x\n", - "qb = 5 # 111 -> 1 + x + x^2\n", - "gx_mask = 3 # 001\n", + "fs = [[1, 1], [1, 1, 1], [1, 0, 0, 1, 0, 0, 1]] # from l=9 code above\n", "\n", - "fs = [3, 7, 73] # from l=9 code above\n", - "gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, l)\n", + "int_gx_mask = HillClimbing.bin_list_to_int(gx_mask)\n", + "int_fs = list(map(HillClimbing.bin_list_to_int, fs))\n", + "gx_bin = CodeConstructor.gx_mask_to_bin(int_gx_mask, int_fs, l)\n", + "a_int = HillClimbing.bin_list_to_int(a)\n", + "b_int = HillClimbing.bin_list_to_int(b)\n", "\n", + "assert CodeConstructor.poly_mod_f2(a_int, gx_bin) == 0\n", + "assert CodeConstructor.poly_mod_f2(b_int, gx_bin) == 0\n", "\n", - "x = [gx_mask, qa, qb] + fs\n", + "params = [gx_mask, a, b] + fs\n", "\n", - "# CodeConstructor.multiply_polynomials_mod_l(gx_bin, n[1], l).bit_count()\n", + "# from Get_new_point_function.get_new_gb_vector -----\n", + "x = np.zeros((3 + len(fs), l), dtype=np.uint8)\n", + "for i, param in enumerate(params):\n", + " x[i, : len(param)] = param\n", + "# ---------------------------------------------------\n", "\n", - "# gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, l)\n", - "target_weight2 = 4\n", + "target_weight2 = 2\n", "\n", "gnp2 = Get_new_points_function(\n", " method=code_class, code_constructor=cc, density=target_weight2, desired_k=desired_k\n", @@ -330,25 +367,27 @@ "\n", "x_tensor = torch.tensor(x, dtype=torch.float32)\n", "\n", - "x_neighbours = hc.swap_factors(x_tensor)\n", + "x_neighbours = hc.mutate_gb(x_tensor)\n", "assert len(x_neighbours) > 0\n", - "print(len(x_neighbours))\n", + "print(f\"neighbours found: {len(x_neighbours)}\")\n", "\n", "for n in x_neighbours:\n", " n = n.to(torch.long).tolist()\n", - " assert n[0] == gx_mask\n", - " assert (n[1] == qa) or (n[2] == qb)\n", - " assert np.array_equal(n[3:], fs)\n", - "\n", - " assert (\n", - " CodeConstructor.multiply_polynomials_mod_l(gx_bin, n[1], l).bit_count()\n", - " == target_weight2\n", - " )\n", - " assert (\n", - " CodeConstructor.multiply_polynomials_mod_l(gx_bin, n[2], l).bit_count()\n", - " == target_weight2\n", - " )\n", - " assert n != x\n", + " padded_gx_mask = gx_mask + ([0] * (l - len(gx_mask)))\n", + " padded_a = a + ([0] * (l - len(a)))\n", + " padded_b = b + ([0] * (l - len(b)))\n", + " padded_fs = list(map(lambda x: x + ([0] * (l - len(x))), fs))\n", + " assert n[0] == padded_gx_mask\n", + "\n", + " if (n[1] == padded_a) & (n[2] == padded_b):\n", + " print(\n", + " \"neighbours contain original\"\n", + " ) # TODO find why neighbour contains original twice\n", + "\n", + " assert (n[1] == padded_a) | (n[2] == padded_b)\n", + " assert n[3:] == padded_fs\n", + " assert sum(n[1]) == target_weight2\n", + " assert sum(n[2]) == target_weight2\n", "\n", " cc.construct(n) # CSSCode has built in checks to make sure the code is valid\n", "\n", From e85d230a4334dadbacb032bd0ee633128b6f74c7 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Wed, 6 May 2026 11:46:10 +0100 Subject: [PATCH 29/36] changed GB representation to flattened binary array as BO code was messing around with tensor shape and breaking things and fixed relevant tests (again) --- BOplayground.py | 36 +++++----- code_construction/code_construction.py | 5 +- tests/test_gb_codes.ipynb | 93 +++++++++++++++++--------- 3 files changed, 82 insertions(+), 52 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index 76fcc4f..90444e6 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -78,14 +78,16 @@ def mutate_gb(self, x: torch.Tensor) -> torch.Tensor: # let d = |j-i| x = x.detach() - a = x[1] + a = x[self.l : 2 * self.l] a_len = len(a) - b = x[2] + b = x[2 * self.l : 3 * self.l] b_len = len(b) - int_fs = list(map(HillClimbing.bin_list_to_int, x[3:].tolist())) - int_gx_mask = HillClimbing.bin_list_to_int(x[0].tolist()) + int_fs = list( + map(HillClimbing.bin_list_to_int, x[3 * self.l :].view(-1, self.l).tolist()) + ) + int_gx_mask = HillClimbing.bin_list_to_int(x[: self.l].tolist()) gx_bin = CodeConstructor.gx_mask_to_bin(int_gx_mask, int_fs, self.l) valid_dists = [] @@ -105,7 +107,7 @@ def mutate_gb(self, x: torch.Tensor) -> torch.Tensor: new_a[one_pos] = 0 new_a[(one_pos + d) % a_len] = 1 new = x.clone() - new[1] = new_a + new[self.l : 2 * self.l] = new_a neigh_list.append(new) if a[(one_pos - d) % a_len].item() == 0 and ((one_pos - d) % a_len) != ( (one_pos + d) % a_len @@ -114,7 +116,7 @@ def mutate_gb(self, x: torch.Tensor) -> torch.Tensor: new_a[one_pos] = 0 new_a[(one_pos - d) % a_len] = 1 new = x.clone() - new[1] = new_a + new[self.l : 2 * self.l] = new_a neigh_list.append(new) b_ones = (b == 1).nonzero().squeeze().tolist() @@ -125,7 +127,7 @@ def mutate_gb(self, x: torch.Tensor) -> torch.Tensor: new_b[one_pos] = 0 new_b[(one_pos + d) % b_len] = 1 new = x.clone() - new[1] = new_b + new[2 * self.l : 3 * self.l] = new_b neigh_list.append(new) if b[(one_pos - d) % b_len].item() == 0 and ((one_pos - d) % b_len) != ( (one_pos + d) % b_len @@ -134,7 +136,7 @@ def mutate_gb(self, x: torch.Tensor) -> torch.Tensor: new_b[one_pos] = 0 new_b[(one_pos - d) % b_len] = 1 new = x.clone() - new[1] = new_b + new[2 * self.l : 3 * self.l] = new_b neigh_list.append(new) if not neigh_list: @@ -1037,11 +1039,9 @@ def get_new_gb_vector(self, number): ] + list(map(Get_new_points_function.int_to_bin_list, self.factors)) for i, param in enumerate(parameters): - padded_arr[i, : len(param)] = ( - param # zero pad parameter matrix, works as binary is lsb first - ) + padded_arr[i, : len(param)] = param # padded so each param len l - results.append(padded_arr) + results.append(padded_arr.ravel()) number -= 1 return np.array(results) @@ -1165,10 +1165,6 @@ def get_args(): args = get_args() - import time - - start_time = time.perf_counter() - if args.distance_exact or args.distance_heuristic: code_eval_metric = "distance" else: @@ -1268,12 +1264,16 @@ def get_args(): y, pl = obj_func(x) y_init.append(y) pl_init.append(pl) - print(f"Evaluated initial code {i}/{init_num} (score={y:.4f})") + print(f"Evaluated initial code {i + 1}/{init_num} (score={y:.4f})") with open(init_data_file, "wb") as f: pickle.dump({"X": X_init, "y": y_init, "pl": pl_init}, f) print(f"Saved initial points to {init_data_file}") + import time + + start_time = time.perf_counter() + print(f"Loading initial codes from {init_data_file}") with open(init_data_file, "rb") as f: data = pickle.load(f) @@ -1357,7 +1357,7 @@ def code_validator(candidate_np): ) # assemble BO - bo_iterations = 10 + bo_iterations = 50 bo = BO_on_QEC( gp=model, gp_trainer=trainer, diff --git a/code_construction/code_construction.py b/code_construction/code_construction.py index d9384ae..b9ad3af 100644 --- a/code_construction/code_construction.py +++ b/code_construction/code_construction.py @@ -493,7 +493,7 @@ def _build_bicycle_css_code(self, A, B) -> CSSCode: def generalised_bicycle_code(self, parameters) -> CSSCode: """ - parameters = [gx_mask, a, b, f1, ..., fn] + parameters = [gx_mask, a, b, f1, ..., fn] (but flattened) gx_mask (list[binary]): binary mask of which irreducable factors of (x^l - 1) make up g(x) a (list[binary]): binary representation of polynomial a(x) b (list[binary]): binary representation of polynomial b(x) @@ -501,7 +501,8 @@ def generalised_bicycle_code(self, parameters) -> CSSCode: """ l = self.para_dict["l"] - a, b = parameters[1:3] + a = parameters[l : 2 * l] + b = parameters[2 * l : 3 * l] a_array = np.array(a) b_array = np.array(b) diff --git a/tests/test_gb_codes.ipynb b/tests/test_gb_codes.ipynb index d16d4ea..5367dd9 100644 --- a/tests/test_gb_codes.ipynb +++ b/tests/test_gb_codes.ipynb @@ -128,9 +128,22 @@ "metadata": {}, "outputs": [], "source": [ - "# TODO: test these\n", - "# _generate_candidate_poly\n", - "# poly_mod_f2" + "# TEST: candidate polynomials have the correct weight and length\n", + "\n", + "para_dict = {\"l\": l}\n", + "code_class = \"gb\"\n", + "target_weight = 3\n", + "desired_k = 4\n", + "\n", + "cc = CodeConstructor(method=code_class, para_dict=para_dict)\n", + "\n", + "gnp = Get_new_points_function(\n", + " method=code_class, code_constructor=cc, density=target_weight, desired_k=desired_k\n", + ")\n", + "gnp.set_gx_mask()\n", + "\n", + "assert gnp._generate_candidate_poly(l).bit_count() == target_weight\n", + "print(\"Test Passed!\")" ] }, { @@ -139,6 +152,24 @@ "id": "6", "metadata": {}, "outputs": [], + "source": [ + "# TEST: can correctly find one polynomial mod another\n", + "p1 = 49 # 100011 -> 1 + x^4 + x^5\n", + "p2 = CodeConstructor.multiply_polynomials_mod_l(p1, 5, l)\n", + "assert CodeConstructor.poly_mod_f2(p1, p1) == 0\n", + "\n", + "p3 = 12 # 0011 -> x^2 + x^3\n", + "# p1 = (p3 * x^2) + 1\n", + "assert CodeConstructor.poly_mod_f2(p1, p3) == 1\n", + "print(\"Test Passed!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], "source": [ "# TEST: are randomly generated GB codes valid\n", "\n", @@ -151,28 +182,18 @@ " return res\n", "\n", "\n", - "para_dict = {\"l\": l}\n", - "code_class = \"gb\"\n", - "target_weight = 3\n", - "desired_k = 4\n", - "\n", - "cc = CodeConstructor(method=code_class, para_dict=para_dict)\n", - "\n", - "gnp = Get_new_points_function(\n", - " method=code_class, code_constructor=cc, density=target_weight, desired_k=desired_k\n", - ")\n", - "\n", - "gnp.set_gx_mask()\n", - "\n", "x_exp_l_minus_1 = 1 + (1 << l) # x^l - 1 = x^l + 1 in binary ring\n", "\n", "new_points = gnp.get_new_points_function(5)\n", "\n", "for point in new_points:\n", - " gx_mask, a, b = point[:3]\n", + " gx_mask = point[:l]\n", + " a = point[l : 2 * l]\n", + " b = point[2 * l : 3 * l]\n", + "\n", " int_gx_mask = HillClimbing.bin_list_to_int(gx_mask)\n", "\n", - " fs = point[3:]\n", + " fs = point[3 * l :].reshape(-1, l)\n", " int_fs = list(map(HillClimbing.bin_list_to_int, fs))\n", "\n", " prod_fs = 1\n", @@ -192,7 +213,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7", + "id": "8", "metadata": {}, "outputs": [], "source": [ @@ -216,7 +237,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8", + "id": "9", "metadata": {}, "outputs": [], "source": [ @@ -238,7 +259,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9", + "id": "10", "metadata": {}, "outputs": [], "source": [ @@ -264,7 +285,8 @@ "# from Get_new_point_function.get_new_gb_vector -----\n", "x = np.zeros((3 + len(fs), l), dtype=np.uint8)\n", "for i, param in enumerate(params):\n", - " x[i, : len(param)] = param\n", + " x[i, : len(param)] = param # padded so each param len l\n", + "x = x.ravel()\n", "# ---------------------------------------------------\n", "\n", "a_padded = a + ([0] * (l - len(a)))\n", @@ -318,7 +340,7 @@ { "cell_type": "code", "execution_count": null, - "id": "10", + "id": "11", "metadata": {}, "outputs": [], "source": [ @@ -347,6 +369,7 @@ "x = np.zeros((3 + len(fs), l), dtype=np.uint8)\n", "for i, param in enumerate(params):\n", " x[i, : len(param)] = param\n", + "x = x.ravel()\n", "# ---------------------------------------------------\n", "\n", "target_weight2 = 2\n", @@ -377,17 +400,23 @@ " padded_a = a + ([0] * (l - len(a)))\n", " padded_b = b + ([0] * (l - len(b)))\n", " padded_fs = list(map(lambda x: x + ([0] * (l - len(x))), fs))\n", - " assert n[0] == padded_gx_mask\n", "\n", - " if (n[1] == padded_a) & (n[2] == padded_b):\n", - " print(\n", - " \"neighbours contain original\"\n", - " ) # TODO find why neighbour contains original twice\n", + " n_g = n[:l]\n", + " n_a = n[l : 2 * l]\n", + " n_b = n[2 * l : 3 * l]\n", + " n_fs = x[3 * l :].reshape(-1, l)\n", + "\n", + " assert n_g == padded_gx_mask\n", + "\n", + " # if (n_a == padded_a) & (n_b == padded_b):\n", + " # print(\n", + " # \"neighbours contain original\"\n", + " # )\n", "\n", - " assert (n[1] == padded_a) | (n[2] == padded_b)\n", - " assert n[3:] == padded_fs\n", - " assert sum(n[1]) == target_weight2\n", - " assert sum(n[2]) == target_weight2\n", + " assert (n_a == padded_a) ^ (n_b == padded_b)\n", + " assert np.array_equal(n_fs, padded_fs)\n", + " assert sum(n_a) == target_weight2\n", + " assert sum(n_b) == target_weight2\n", "\n", " cc.construct(n) # CSSCode has built in checks to make sure the code is valid\n", "\n", From 4f523373aadacf706214df4682f192c6bf4d89e6 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Sat, 9 May 2026 16:58:00 +0100 Subject: [PATCH 30/36] fixed edge case where no neighbours can be found, prints warning and continues with current best neighbour --- BOplayground.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/BOplayground.py b/BOplayground.py index 90444e6..b965e68 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -206,6 +206,11 @@ def __call__(self, gp) -> torch.Tensor: while True: nbrs = self.mutate(best_neighbor).to(self.device) acq_vals = self.acquisition(nbrs, gp).reshape(-1) # [d] + if acq_vals.numel() == 0: + print("WARNING: no neigbours could be found for the following code:") + print(best_neighbor.tolist()) + break + top_val, top_idx = torch.topk(acq_vals, k=1) top_val = top_val[0] top_neighbor = nbrs[top_idx[0]] From 6c5d6efc79bae7756d11eaecf698370f7034642a Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Mon, 11 May 2026 15:59:04 +0100 Subject: [PATCH 31/36] reverted floats to 32bit from 64, tidied up, read_results can now estimate distance of codes saved from BO output --- BOplayground.py | 67 ++++---------------------- bayesian_optimization/bo.py | 6 +-- code_construction/code_construction.py | 12 ----- read_results.ipynb | 55 ++++++++++++++++++++- 4 files changed, 66 insertions(+), 74 deletions(-) diff --git a/BOplayground.py b/BOplayground.py index b965e68..9cbd842 100644 --- a/BOplayground.py +++ b/BOplayground.py @@ -144,59 +144,10 @@ def mutate_gb(self, x: torch.Tensor) -> torch.Tensor: return torch.stack(neigh_list, dim=0) - # def swap_factors(self, x: torch.Tensor) -> torch.Tensor: - # x = x.detach() - - # gx_mask = int(x[0].item()) - # qa = int(x[1].item()) - # qb = int(x[2].item()) - # fs = [int(f.item()) for f in x[3:]] - - # gx_bin = CodeConstructor.gx_mask_to_bin(gx_mask, fs, self.l) - - # max_bound = self.l - (gx_bin.bit_length() - 1) - - # neigh_list = [] - # for i in range(max_bound): - # for j in range(i, max_bound): - # if i == j: - # new_qa = qa ^ (1 << i) # flips one factor of qa - # new_qb = qb ^ (1 << i) - # else: - # new_qa = qa ^ (1 << i) ^ (1 << j) # flips two factors of qa - # new_qb = qb ^ (1 << i) ^ (1 << j) - - # a_weight = CodeConstructor.multiply_polynomials_mod_l( - # gx_bin, new_qa, self.l - # ).bit_count() - - # b_weight = CodeConstructor.multiply_polynomials_mod_l( - # gx_bin, new_qb, self.l - # ).bit_count() - - # if ( - # a_weight == self.target_row_weight - # ): # checks if new a(x) preserves LDPC density - # n1 = x.clone() - # n1[1] = new_qa - # neigh_list.append(n1) - - # if ( - # b_weight == self.target_row_weight - # ): # checks if new b(x) preserves LDPC density - # n2 = x.clone() - # n2[2] = new_qb - # neigh_list.append(n2) - - # if not neigh_list: - # return torch.empty((0, len(x)), dtype=x.dtype, device=x.device) - - # return torch.stack(neigh_list, dim=0) - @torch.no_grad() def __call__(self, gp) -> torch.Tensor: X0_np = self.gnp(self.next_points_num) # [n, d], 0/1 - X0 = torch.tensor(X0_np, dtype=torch.float64, device=self.device) + X0 = torch.tensor(X0_np, dtype=torch.float32, device=self.device) best_list = [] for x in X0: # x: [d] @@ -207,7 +158,9 @@ def __call__(self, gp) -> torch.Tensor: nbrs = self.mutate(best_neighbor).to(self.device) acq_vals = self.acquisition(nbrs, gp).reshape(-1) # [d] if acq_vals.numel() == 0: - print("WARNING: no neigbours could be found for the following code:") + print( + "WARNING: no neigbours could be found for the following code:" + ) print(best_neighbor.tolist()) break @@ -223,7 +176,7 @@ def __call__(self, gp) -> torch.Tensor: best_list.append(best_neighbor) - cand = torch.stack(best_list, dim=0) # [n, d], float64, 0/1 + cand = torch.stack(best_list, dim=0) # [n, d], float32, 0/1 if self.validator is not None: cand_np = cand.detach().cpu().numpy().astype(np.int64) @@ -235,9 +188,9 @@ def __call__(self, gp) -> torch.Tensor: cand_np[i] = self.gnp(1) if tries > 1000: break - cand = torch.tensor(cand_np, dtype=torch.float64, device=self.device) + cand = torch.tensor(cand_np, dtype=torch.float32, device=self.device) - return cand # [n, d], float64(0/1), on device + return cand # [n, d], float32(0/1), on device class EIAcquisitionFunction: @@ -357,10 +310,10 @@ def __call__(self, X, gp: torch.nn.Module): # Ensure X is a proper tensor on the correct device if not torch.is_tensor(X): - X = torch.tensor(X, dtype=torch.float64) + X = torch.tensor(X, dtype=torch.float32) if X.ndim == 1: X = X.unsqueeze(0) - X = X.to(self.device, dtype=torch.float64) + X = X.to(self.device, dtype=torch.float32) # Evaluation mode for GP gp.eval() @@ -1286,7 +1239,7 @@ def get_args(): y_init = data["y"] pl_init = data["pl"] - X_init = torch.tensor(X_init, dtype=torch.float64).to(DEVICE) + X_init = torch.tensor(X_init, dtype=torch.float32).to(DEVICE) y_init = torch.tensor(y_init, dtype=torch.float32).to(DEVICE) pl_init = torch.tensor(pl_init, dtype=torch.float32).to(DEVICE) # get gp model diff --git a/bayesian_optimization/bo.py b/bayesian_optimization/bo.py index df10cf5..30bca2c 100644 --- a/bayesian_optimization/bo.py +++ b/bayesian_optimization/bo.py @@ -104,7 +104,7 @@ def __init__( self.device = device self.code_eval_metric = code_eval_metric - self.X = initial_X.to(self.device, dtype=torch.float64) + self.X = initial_X.to(self.device, dtype=torch.float32) # if code_eval_metric is distance, pl is true distance rather than true pl (logical error rate) # all similarly named pl variables will also refer to distance rather than LER (TODO fix varibale names) self.pl = initial_pl.to( @@ -155,7 +155,7 @@ def _latent_posterior(self, X_tensor: torch.Tensor): self.gp.eval() if hasattr(self.gp, "likelihood") and self.gp.likelihood is not None: self.gp.likelihood.eval() - X_tensor = X_tensor.to(self.device, dtype=torch.float64) + X_tensor = X_tensor.to(self.device, dtype=torch.float32) with gpytorch.settings.fast_pred_var(): if hasattr(self.gp, "posterior"): post = self.gp.posterior(X_tensor) @@ -207,7 +207,7 @@ def run(self): # --- Step 1: suggest candidates --- t0 = time.time() - next_points = self.suggest_next(self.gp) # torch.float64, [n, d], on device + next_points = self.suggest_next(self.gp) # torch.float32, [n, d], on device t_next = time.time() # --- Step 1b: pre-evaluation predictions for diagnostics --- diff --git a/code_construction/code_construction.py b/code_construction/code_construction.py index b9ad3af..070782f 100644 --- a/code_construction/code_construction.py +++ b/code_construction/code_construction.py @@ -634,18 +634,6 @@ def corresponding_matrix_BA(self, g_size, A): B_A = np.vstack((B_A, r_matrix)) return B_A - # def corresponding_matrix_Ba(self, g_size, a): - # B_a = np.zeros((g_size, g_size), dtype=int) - # for i in range(g_size): - # B_a = (B_a + a[i] * self.g_power(g_size, i)) % 2 - # return B_a - - # def g_power(self, g_size, p): - # g_p = np.zeros((g_size, g_size), dtype=int) - # for i in range(g_size): - # g_p[i][int((i + p) % g_size)] = 1 - # return g_p - def arbitrary_bivariate_bicycle_code(self, parameters): # a=[a_0,a_1,...,a_{l+g-2}] is a rep of coefficient of A=a_0*I + a_1*x + a_2*x^2 +...+ a_{l-1}*x^{l-1} + a_l*y + a_{l+1}*y^2 +...+ a_{l+g-2}*y^{g-1} l = self.para_dict["l"] diff --git a/read_results.ipynb b/read_results.ipynb index 9a89323..b4944e9 100644 --- a/read_results.ipynb +++ b/read_results.ipynb @@ -8,9 +8,10 @@ "outputs": [], "source": [ "import pickle\n", - "import numpy as np\n", "\n", - "file_path = \"./data/BO_results/GB_BO_results_0_1_4_7_1.pkl\"" + "# file_path = \"./data/BO_results/GB_BO_results_0_1_4_7_1.pkl\"\n", + "# file_path = \"./GB_BO_results_7_1.0_6_72_2100258.pkl\" # 144,32,6\n", + "file_path = \"./GB_BO_results_6_1.0_6_72_33027.pkl\" # 144,30,8" ] }, { @@ -46,6 +47,56 @@ "else:\n", " print(history)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "from code_construction.code_construction import CodeConstructor\n", + "import codedistance\n", + "\n", + "code_class = \"gb\"\n", + "para_dict = {\"l\": 72}\n", + "\n", + "cc = CodeConstructor(method=code_class, para_dict=para_dict)\n", + "best_x = best_x.cpu()\n", + "code = cc.construct(best_x)\n", + "print(f\"n: {code.n}, k: {code.k}\")\n", + "\n", + "res = codedistance.CSScodeDistance(\n", + " code.hx,\n", + " code.hz,\n", + " method=\"QDistEvol\",\n", + " params={},\n", + " seed=100,\n", + ")\n", + "dist = res[\"d\"]\n", + "print(f\"d: {dist}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# file_path = \"./data/BO_initial_points/BO_initial_points_3_1.0.pkl\"\n", + "# # bb codes with l=12, m=6, dataset_index=3, lambda=1,\n", + "# file_path = \"./data/BO_initial_points/BO_initial_points_0_63.pkl\"\n", + "# # bb codes with l=6, m=3, dataset_index=0, lambda=1,\n", + "\n", + "# with open(file_path, \"rb\") as f:\n", + "# data = pickle.load(f)\n", + "# X_init = data[\"X\"]\n", + "# y_init = data[\"y\"]\n", + "# pl_init = data[\"pl\"]\n", + "\n", + "# print(len(X_init))" + ] } ], "metadata": { From 8fa2ca73d6880cebdab183e61b79bfdc59fac95e Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Mon, 18 May 2026 12:55:08 +0100 Subject: [PATCH 32/36] evaluation bug fixes --- bayesian_optimization/objective_function.py | 5 +++-- dataset.py | 2 +- evaluation/decoder_based_evaluation.py | 2 +- readme.ipynb | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bayesian_optimization/objective_function.py b/bayesian_optimization/objective_function.py index d70f541..d576fcb 100644 --- a/bayesian_optimization/objective_function.py +++ b/bayesian_optimization/objective_function.py @@ -314,7 +314,7 @@ def ler(self, css: CSSCode): pL = mc.run( shots=self.decoder_param["trail"], max_error=self.decoder_param["max_error"], - num_workers=self.circuit_param["num_worker"], + num_workers=self.circuit_param["num_workers"], ) else: evaluator = CSS_Evaluator(css.hx, css.hz) @@ -325,7 +325,8 @@ def ler(self, css: CSSCode): ) pL = min(pL, 1 - (1e-8)) pL = max(pL, 1e-20) - return float(pL) + + return float(pL) def nller(self, x): """Compute negative log-likelihood (-log pL).""" diff --git a/dataset.py b/dataset.py index 7cce574..9b5aad5 100644 --- a/dataset.py +++ b/dataset.py @@ -109,7 +109,7 @@ def objectivefunction(code): pL = mc.run( shots=noise_and_decoder_param.get("trail", 100_000), max_error=noise_and_decoder_param.get("max_error", 100), - num_workers=noise_and_decoder_param.get("num_worker", 24), + num_workers=noise_and_decoder_param.get("num_workers", 24), ) return pL diff --git a/evaluation/decoder_based_evaluation.py b/evaluation/decoder_based_evaluation.py index c5ccb6b..d155264 100644 --- a/evaluation/decoder_based_evaluation.py +++ b/evaluation/decoder_based_evaluation.py @@ -378,7 +378,7 @@ def P_L_given_n_e(self, n_e, trail=100): # TODO p_l_ne = 0 - print(n_e) + # print(n_e) for t in range(int(trail * (0.97**n_e))): error_x, error_z = self.Get_error(n_e) diff --git a/readme.ipynb b/readme.ipynb index 721980f..86b70ec 100644 --- a/readme.ipynb +++ b/readme.ipynb @@ -314,7 +314,7 @@ " \"decoder\": \"bplsd\",\n", " \"trail\": 100_000,\n", " \"max_error\": 100,\n", - " \"num_worker\": 24,\n", + " \"num_workers\": 24,\n", " },\n", ")\n", "# this is used to generate the dataset;\n", From ff1e79ecde2a3b89ff851d59754720b6b6929286 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Thu, 21 May 2026 12:04:41 +0100 Subject: [PATCH 33/36] updated requirements and setup to be more robust --- readme2.ipynb | 53 ++++++++++++++++++++++++------------------------ requirements.txt | 15 ++++++-------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/readme2.ipynb b/readme2.ipynb index 35660e4..dc97c80 100644 --- a/readme2.ipynb +++ b/readme2.ipynb @@ -7,7 +7,7 @@ "source": [ "# An instruction of how to set up the environmnet\n", "\n", - "Python=3.10 is required for our experiment, please create a new environment with Python=3.10" + "Please create a new environment with Python=3.11" ] }, { @@ -17,7 +17,7 @@ "metadata": {}, "outputs": [], "source": [ - "!conda create -n qec python=3.10 numpy scipy matplotlib pandas\n", + "!conda create -n qec python=3.11\n", "!conda activate qec" ] }, @@ -44,22 +44,14 @@ "source": [ "## pip libraries:\n", "\n", - "please run these commands in the terminal; or enter the `qec` environment and run the following block in the notebook.\n", + "Please run these commands in the terminal; or enter the `qec` environment and run the following block in the notebook. If you have a different version of cuda on your machine, change the pytorch URL.\n", "\n", "```\n", - "pip install torch==2.6.0 \n", + "pip install -r requirements.txt\n", + "pip install --extra-index-url https://download.pytorch.org/whl/cu126\n", + "pip install topomodelx==0.0.1 --no-deps\n", + "pip install ortools==9.14.6206\n", "\n", - "pip install torch-geometric\n", - "\n", - "pip install gpytorch botorch ax pymatching pymoo qecsim qldpc\n", - "\n", - "pip install toponetx topomodelx topoembedx\n", - "\n", - "pip install stim sinter\n", - "\n", - "pip install ldpc bposd\n", - "\n", - "pip install pymoo\n", "```" ] }, @@ -70,13 +62,10 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install torch==2.6.0 \n", - "!pip install torch-geometric\n", - "!pip install gpytorch botorch ax pymatching pymoo qecsim qldpc\n", - "!pip install toponetx topomodelx topoembedx\n", - "!pip install stim sinter\n", - "!pip install ldpc bposd\n", - "!pip install pymoo" + "!pip install -r requirements.txt\n", + "!pip install --extra-index-url https://download.pytorch.org/whl/cu126\n", + "!pip install topomodelx==0.0.1 --no-deps\n", + "!pip install ortools==9.14.6206" ] }, { @@ -150,11 +139,12 @@ "l = 12\n", "g = 6\n", "dataset_index = 0\n", - "seed = 1\n", - "filename = f\"./data/BO_results/BO_{l}_{g}_{dataset_index}_{seed}.pkl\" # if you want to read the EA/RS results, place replace the prefix 'BO' to 'EA' or 'RS'\n", + "seed = 42\n", + "lamda_ = 1\n", + "filename = f\"./data/BO_results/BO_{l}_{g}_{dataset_index}_{seed}_{lamda_}.pkl\" # if you want to read the EA/RS results, place replace the prefix 'BO' to 'EA' or 'RS'\n", "\n", "best_known_y = -0.00693 # The objective function of [[144,26]] code.\n", - "with open(filename, \"rs\") as f:\n", + "with open(filename, \"rb\") as f:\n", " data = pickle.load(f)\n", " flat = data[\"evaluation_history\"] # this is used to plot the best-so-far curve\n", " best_y = data[\n", @@ -245,7 +235,16 @@ " 0.00011340403922088793,\n", " 5.5016644518901536e-05,\n", " 1.8335182204287648e-05,\n", - "]" + "]\n", + "\n", + "import matplotlib.pyplot as plt\n", + "\n", + "plt.plot(pp_list, gross, label=\"gross\")\n", + "plt.plot(pp_list, code_144_26, label=\"144,8\")\n", + "plt.yscale(\"log\")\n", + "plt.xscale(\"log\")\n", + "plt.legend()\n", + "plt.show()" ] }, { @@ -306,7 +305,6 @@ " 2.5000218752957437e-06,\n", "]\n", "# These data was calculated from 100_000 Monte-Carlo decodings, the standard error will be sqrt(wer(1-wer)/100_000)\n", - "import matplotlib.pyplot as plt\n", "\n", "plt.plot(pp_list, gross, label=\"gross\")\n", "plt.plot(pp_list, code_144_8, label=\"144,8\")\n", @@ -339,6 +337,7 @@ "g = 6\n", "para_dict = {\"l\": l, \"g\": g}\n", "code_class = \"bb\"\n", + "pp = 0.05\n", "from code_construction.code_construction import CodeConstructor\n", "\n", "code_constructor = CodeConstructor(method=code_class, para_dict=para_dict)\n", diff --git a/requirements.txt b/requirements.txt index a561bf0..ffa2216 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,16 @@ botorch==0.16.1 bposd==2.1 +codedistance @ git+https://github.com/SpyroL7/codeDistancePYPI.git@v0.1.1 Evaluate==0.4.6 gpytorch==1.15.1 ldpc==2.4.1 matplotlib==3.8.4 -numpy==2.4.2 -pandas==3.0.0 -pyldpc==0.7.9 +numpy==2.2.6 pymoo==0.6.1.6 -scikit_learn==1.8.0 -scipy==1.17.0 +qLDPC @ git+https://github.com/qLDPCOrg/qLDPC.git@main +scipy==1.17.1 sinter==1.15.0 stim==1.16.dev1770241571 -TopoModelX==0.0.1 -TopoNetX==0.2.0 -torch==2.5.1 #+cu121 +sympy==1.13.1 torch_geometric==2.7.0 -tqdm==4.67.3 +tqdm==4.67.3 \ No newline at end of file From 0a70426ca77efb7ea73caf27c86d14e13abf3311 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Fri, 5 Jun 2026 13:25:16 +0100 Subject: [PATCH 34/36] small installation fix --- readme2.ipynb | 4 ++-- requirements.txt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/readme2.ipynb b/readme2.ipynb index dc97c80..ff80b84 100644 --- a/readme2.ipynb +++ b/readme2.ipynb @@ -48,7 +48,7 @@ "\n", "```\n", "pip install -r requirements.txt\n", - "pip install --extra-index-url https://download.pytorch.org/whl/cu126\n", + "pip install torch --extra-index-url https://download.pytorch.org/whl/cu126\n", "pip install topomodelx==0.0.1 --no-deps\n", "pip install ortools==9.14.6206\n", "\n", @@ -63,7 +63,7 @@ "outputs": [], "source": [ "!pip install -r requirements.txt\n", - "!pip install --extra-index-url https://download.pytorch.org/whl/cu126\n", + "!pip install torch --extra-index-url https://download.pytorch.org/whl/cu126\n", "!pip install topomodelx==0.0.1 --no-deps\n", "!pip install ortools==9.14.6206" ] diff --git a/requirements.txt b/requirements.txt index ffa2216..1e94c2e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,5 +12,6 @@ scipy==1.17.1 sinter==1.15.0 stim==1.16.dev1770241571 sympy==1.13.1 +TopoNetX==0.2.0 torch_geometric==2.7.0 tqdm==4.67.3 \ No newline at end of file From 3ca3c6c3626d7d3a17377b62f4b9e7ddd3ac8bea Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Fri, 5 Jun 2026 15:08:29 +0100 Subject: [PATCH 35/36] notebooks and scripts for evaluating qeccs and making graphs --- generate_graphs.ipynb | 277 ++++++++++++++++++++++++++++++++++ read_results.ipynb | 335 ++++++++++++++++++++++++++++++++++++++---- true_dist.py | 110 ++++++++++++++ 3 files changed, 691 insertions(+), 31 deletions(-) create mode 100644 generate_graphs.ipynb create mode 100644 true_dist.py diff --git a/generate_graphs.ipynb b/generate_graphs.ipynb new file mode 100644 index 0000000..ce84fb1 --- /dev/null +++ b/generate_graphs.ipynb @@ -0,0 +1,277 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "0", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from scipy.special import comb" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "### Scatter graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# n=144 Hamming Bound\n", + "def get_hamming_bound(n: int):\n", + " n = 144\n", + " t_values = np.arange(0, 31)\n", + " R_bound = []\n", + " t_norm = []\n", + "\n", + " for t in t_values:\n", + " sum_term = sum([comb(n, j, exact=True) * (3**j) for j in range(t + 1)])\n", + " f2_t = (1 / n) * np.log2(float(sum_term))\n", + " \n", + " R = 1.0 - f2_t\n", + " if R >= 0:\n", + " R_bound.append(R)\n", + " t_norm.append(t / n)\n", + " \n", + " return R_bound, t_norm\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "# GB\n", + "\n", + "# green markers: lambda = 0.5 \n", + "lambda_1_rate = [\n", + " 0.3333333333,\n", + " 0.2222222222,\n", + " 0.2152777778,\n", + " 0.2916666667,\n", + "]\n", + "\n", + "lambda_1_ptnorm = [\n", + " 2.459,\n", + " 4.852,\n", + " 5.035,\n", + " 3.655,\n", + "]\n", + "\n", + "# red markers: lambda = 1.0\n", + "lambda_05_rate = [\n", + " 0.09722222222,\n", + " 0.1111111111,\n", + " 0.1666666667,\n", + " 0.25,\n", + " 0.25,\n", + " 0.1666666667,\n", + " 0.2083333333,\n", + " 0.2222222222,\n", + " 0.2222222222,\n", + " 0.25,\n", + " 0.2777777778,\n", + " 0.2083333333,\n", + "]\n", + "\n", + "lambda_05_ptnorm = [\n", + " 6.798,\n", + " 6.537,\n", + " 5.193,\n", + " 3.99,\n", + " 5.031,\n", + " 5.186,\n", + " 4.729,\n", + " 5.01,\n", + " 3.614,\n", + " 3.827,\n", + " 3.788,\n", + " 4.302,\n", + "]\n", + "\n", + "lambda_1_ptnorm = np.array(lambda_1_ptnorm) / 144\n", + "lambda_05_ptnorm = np.array(lambda_05_ptnorm) / 144\n", + "\n", + "# black marker: Gross code benchmark\n", + "gross_code = (12/144, 0.042) # [[144, 12, 12]]\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "n = 144\n", + "R_bound, t_norm = get_hamming_bound(n)\n", + "\n", + "plt.figure(figsize=(8, 6))\n", + "plt.plot(R_bound, t_norm, '-', color='tab:blue', label=f'n={n} Hamming bound')\n", + "# plt.scatter(lambda_05_k_16[0], lambda_05_k_16[1], color='tab:green', marker='x', s=60, label='[[144, 16]]')\n", + "# plt.scatter(lambda_1_k_36[0], lambda_1_k_36[1], color='tab:red', marker='x', s=60, label='[[144, 36]]')\n", + "\n", + "plt.scatter(lambda_05_rate, lambda_05_ptnorm, color='tab:green', marker='+', s=60, label='lambda = 0.5')\n", + "plt.scatter(lambda_1_rate, lambda_1_ptnorm, color='tab:red', marker='+', s=60, label='lambda = 1')\n", + "\n", + "plt.scatter(gross_code[0], gross_code[1], color='black', marker='x', s=60, label='Gross code [[144, 12]]')\n", + "\n", + "plt.xlabel('Code Rate $R = k/n$', fontsize=12)\n", + "plt.ylabel('Normalized pseudo-distance $\\hat{t}/n$', fontsize=12)\n", + "plt.title('Operating Points of Generalised Bicycle Codes', fontsize=14)\n", + "# plt.title('Operating Points of Bivariate Bicycle Codes (found in distance mode)', fontsize=14)\n", + "plt.legend(fontsize=11)\n", + "plt.grid(True, linestyle='--', alpha=0.5)\n", + "\n", + "plt.xlim(-0.05, 0.85)\n", + "plt.ylim(0.00, 0.21)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "### Step graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "import re\n", + "\n", + "def extract_best_values(file_path):\n", + " pattern = re.compile(r\"\\(Best value:\\s*(-?\\d+\\.\\d+)\\)\")\n", + " best_values = []\n", + " \n", + " with open(file_path, 'r') as file:\n", + " for line in file:\n", + " match = pattern.search(line)\n", + " if match:\n", + " val = float(match.group(1))\n", + " best_values.append(val)\n", + " \n", + " return best_values\n", + "\n", + "\n", + "path = \"./bayopLTEg12.e2728217.5.txt\" \n", + "y_data = extract_best_values(path)\n", + "\n", + "x_data = [i for i in range(0, (len(y_data) * 2), 2)]\n", + "\n", + "print(f\"Extracted {len(y_data)} data points.\")\n", + "print(\"First 5 X-coordinates:\", x_data[:7])\n", + "print(\"First 5 Y-coordinates:\", y_data[:7])\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "def process_all_runs(folder_path):\n", + " all_runs = []\n", + " \n", + " for file_name in os.listdir(folder_path):\n", + " file_path = os.path.join(folder_path, file_name)\n", + " run_data = extract_best_values(file_path)\n", + " \n", + " if len(run_data) == 101:\n", + " all_runs.append(run_data)\n", + " elif len(run_data) == 200:\n", + " all_runs.append(run_data[::2] + [run_data[-1]])\n", + " else:\n", + " print(f\"{file_name} has {len(run_data)} points instead of 100, skipping\")\n", + "\n", + " if not all_runs:\n", + " print(\"no files found\")\n", + " return\n", + "\n", + " all_runs_matrix = np.array(all_runs)\n", + " mean_line = np.mean(all_runs_matrix, axis=0)\n", + " std_line = np.std(all_runs_matrix, axis=0)\n", + "\n", + " return mean_line, std_line\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "fig, ax = plt.subplots(figsize=(8, 5))\n", + "ax.set_ylim(-0.62, -0.18)\n", + "ax.set_xticks(np.arange(0, 201, 50))\n", + "\n", + "folders = {\n", + " \"./logs/g12\": \"GB-BO mean: k=12, density=6\",\n", + " \"./logs/g2\": \"GB-BO mean: varied params\",\n", + " \"./logs/l1_12_6\": \"BB-BO mean\",\n", + " \"./logs/RS_gb\": \"GB-RS mean\",\n", + "}\n", + "\n", + "for folder, descr in folders.items():\n", + " mean, std = process_all_runs(folder)\n", + "\n", + " ax.step(x_data, mean, label=descr, where='post')\n", + "\n", + " ax.fill_between(x_data, mean - std, mean + std, \n", + " alpha=0.2, step='post')\n", + "\n", + "ax.set_xlabel('Evaluation index', fontsize=12)\n", + "ax.set_ylabel('Best-so-far objective', fontsize=12)\n", + "ax.legend(loc='lower right', fontsize=10, framealpha=0.4)\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qec", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.19" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/read_results.ipynb b/read_results.ipynb index b4944e9..a2756aa 100644 --- a/read_results.ipynb +++ b/read_results.ipynb @@ -9,9 +9,7 @@ "source": [ "import pickle\n", "\n", - "# file_path = \"./data/BO_results/GB_BO_results_0_1_4_7_1.pkl\"\n", - "# file_path = \"./GB_BO_results_7_1.0_6_72_2100258.pkl\" # 144,32,6\n", - "file_path = \"./GB_BO_results_6_1.0_6_72_33027.pkl\" # 144,30,8" + "file_path = \"NewResults/GB_BO_results_6_1.0_6_72_33027.pkl\"" ] }, { @@ -55,26 +53,23 @@ "metadata": {}, "outputs": [], "source": [ - "from code_construction.code_construction import CodeConstructor\n", + "from code_construction.code_construction import CodeConstructor, CSSCode\n", "import codedistance\n", "\n", - "code_class = \"gb\"\n", - "para_dict = {\"l\": 72}\n", + "cc = CodeConstructor(method=\"bb\", para_dict={\"l\": 12, \"g\": 6})\n", + "cc2 = CodeConstructor(method=\"gb\", para_dict={\"l\":72})\n", "\n", - "cc = CodeConstructor(method=code_class, para_dict=para_dict)\n", - "best_x = best_x.cpu()\n", - "code = cc.construct(best_x)\n", - "print(f\"n: {code.n}, k: {code.k}\")\n", "\n", - "res = codedistance.CSScodeDistance(\n", - " code.hx,\n", - " code.hz,\n", - " method=\"QDistEvol\",\n", - " params={},\n", - " seed=100,\n", - ")\n", - "dist = res[\"d\"]\n", - "print(f\"d: {dist}\")" + "def get_nkd(code: CSSCode):\n", + " res = codedistance.CSScodeDistance(\n", + " code.hx,\n", + " code.hz,\n", + " method=\"QDistEvol\",\n", + " params={},\n", + " seed=100,\n", + " )\n", + " dist = res[\"d\"]\n", + " return code.n, code.k, dist" ] }, { @@ -84,18 +79,296 @@ "metadata": {}, "outputs": [], "source": [ - "# file_path = \"./data/BO_initial_points/BO_initial_points_3_1.0.pkl\"\n", - "# # bb codes with l=12, m=6, dataset_index=3, lambda=1,\n", - "# file_path = \"./data/BO_initial_points/BO_initial_points_0_63.pkl\"\n", - "# # bb codes with l=6, m=3, dataset_index=0, lambda=1,\n", - "\n", - "# with open(file_path, \"rb\") as f:\n", - "# data = pickle.load(f)\n", - "# X_init = data[\"X\"]\n", - "# y_init = data[\"y\"]\n", - "# pl_init = data[\"pl\"]\n", - "\n", - "# print(len(X_init))" + "from bayesian_optimization.objective_function import ObjectiveFunction\n", + "\n", + "of1 = ObjectiveFunction(\n", + " cc,\n", + " lambda_=1,\n", + " pp=0.05,\n", + " decoder_param={\"trail\": 100_000, \"max_error\": 1000},\n", + ")\n", + "of2 = ObjectiveFunction(\n", + " cc2,\n", + " lambda_=1,\n", + " pp=0.05,\n", + " decoder_param={\"trail\": 100_000, \"max_error\": 1000},\n", + ")\n", + "\n", + "# of_cl = ObjectiveFunction(\n", + "# cc,\n", + "# lambda_=1,\n", + "# pp=0.005,\n", + "# circuit_level_noise=True,\n", + "# decoder_param={\"trail\": 100_000, \"max_error\": 1000},\n", + "# )\n", + "\n", + "pp_list = [\n", + " 0.05,\n", + " 0.040036870145840404,\n", + " 0.032059019421497734,\n", + " 0.0256708559516296,\n", + " 0.020555614525359374,\n", + " 0.01645964939039528,\n", + " 0.013179856905786339,\n", + " 0.010553604389554513,\n", + " 0.008450665770303305,\n", + " 0.0067667641618306355,\n", + "]\n", + "\n", + "gross_code = [\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 0.0,\n", + " 0.0,\n", + "]\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "code = cc.construct(gross_code)\n", + "\n", + "n, k, d = get_nkd(code)\n", + "print(f\"[[{n}, {k}, {d}]]\")\n", + "\n", + "ler_plq = of1.lerpq(gross_code)\n", + "print(f\"LER per logical qubit: {ler_plq}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "def get_row_column_weights(H):\n", + " row_weights = np.sum(H != 0, axis=1)\n", + " max_row_w = np.max(row_weights)\n", + " avg_row_w = np.mean(row_weights)\n", + "\n", + " col_weights = np.sum(H != 0, axis=0)\n", + " max_col_w = np.max(col_weights)\n", + " avg_col_w = np.mean(col_weights)\n", + "\n", + " return max_row_w, avg_row_w, max_col_w, avg_col_w\n", + "\n", + "\n", + "def percent_diff(v1, v2):\n", + " return ((v2 - v1) / v1) * 100" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "files = [\n", + " None,\n", + " \"NewResults\\handpicked\\GB_BO_results_0_0.5_4_72_10246.pkl\",\n", + " \"NewResults\\handpicked\\GB_BO_results_1_0.5_4_72_8595.pkl\",\n", + " \"NewResults\\handpicked\\GB_BO_results_4_0.5_6_72_16549.pkl\",\n", + " \"NewResults\\handpicked\\GB_BO_results_11_0.5_8_72_526394.pkl\",\n", + "]\n", + "\n", + "ler_results = []\n", + "\n", + "for file in files:\n", + " if file is None:\n", + " code = cc.construct(gross_code)\n", + " best_x = gross_code\n", + " best_y = 0\n", + " of = of1\n", + " else:\n", + " with open(file, \"rb\") as f:\n", + " results = pickle.load(f)\n", + " best_x = results[\"best_x\"]\n", + " best_y = results[\"best_y\"]\n", + "\n", + " best_x = best_x.cpu()\n", + " \n", + " if \"GB\" in file:\n", + " code = cc2.construct(best_x)\n", + " of = of2\n", + " else:\n", + " code = cc.construct(best_x)\n", + " of = of1\n", + "\n", + " n, k, d = get_nkd(code)\n", + "\n", + " of.pp = pp_list[0]\n", + " ler = of.ler(code)\n", + " ler_pq = of.lerpq(best_x)\n", + " \n", + " # th, pl = of.psuedo_t(code)\n", + "\n", + " print(f\"from {file}, score: {best_y:.5f}\")\n", + " print(f\"[[{n}, {k}, {d}]]\")\n", + " print(f\"LER per logical qubit (pp=0.05): {ler_pq}\")\n", + " mrx, arx, mcx, acx = get_row_column_weights(code.hx)\n", + " mrz, arz, mcz, acz = get_row_column_weights(code.hz)\n", + " print(f\"Hx: max row weight={mrx}, avg row weight={arx}\")\n", + " print(f\"Hx: max col weight={mcx}, avg col weight={acx}\")\n", + " print(f\"Hz: max row weight={mrz}, avg row weight={arz}\")\n", + " print(f\"Hz: max col weight={mcz}, avg col weight={acz}\")\n", + " # print(f\"pseduo-t: h-hat={th}, pL={pl}\")\n", + " print(\"--------------------------------------------\")\n", + "\n", + " res = []\n", + " res.append(ler_pq)\n", + " with open(\"line_res_gb.txt\", 'a') as f:\n", + " f.write(f\"\\n[[{n}, {k}, {d}]], {best_y:.5f}\\n\")\n", + " f.write(f\"{ler_pq}\\n\")\n", + "\n", + " for i, p in enumerate(pp_list[1:]):\n", + " of.pp = p\n", + " ler_pq = of.lerpq(best_x)\n", + " res.append(ler_pq)\n", + " with open(\"line_res_gb.txt\", 'a') as f:\n", + " f.write(f\"{ler_pq}\\n\")\n", + "\n", + " ler_results.append(res)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "import re\n", + "\n", + "# above code often got interrupted, so this function loads ler_results from a file\n", + "def parse_line_res(file_path):\n", + " blocks = []\n", + " current_block = []\n", + " current_header = None\n", + " block_start_pattern = re.compile(r\"^\\s*(\\[\\[.*?\\]\\])\")\n", + " \n", + " with open(file_path, 'r') as file:\n", + " for line in file:\n", + " line = line.strip()\n", + " if not line:\n", + " continue\n", + " \n", + " match = block_start_pattern.match(line)\n", + " if match:\n", + " if current_header and current_block:\n", + " blocks.append({'label': current_header, 'data': current_block})\n", + " current_block = []\n", + "\n", + " current_header = match.group(1)\n", + " continue\n", + " \n", + " try:\n", + " val = float(line)\n", + " current_block.append(val)\n", + " except ValueError:\n", + " print(f\"Skipping unparseable line: {line}\")\n", + " \n", + " if current_header and current_block:\n", + " blocks.append({'label': current_header, 'data': current_block})\n", + " \n", + " return blocks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "ler_results = parse_line_res(\"line_res_gb.txt\")\n", + "\n", + "for i, r in enumerate(ler_results):\n", + " if i == 0:\n", + " label = r['label'] + \" Gross code\"\n", + " else:\n", + " label = \"GB \" + r['label']\n", + " plt.plot(pp_list, r['data'], label=label)\n", + "\n", + "plt.xlabel('Physical Error Rate', fontsize=12)\n", + "plt.ylabel('Logical Error Rate per Logical Qubit', fontsize=12)\n", + "\n", + "plt.yscale(\"log\")\n", + "plt.xscale(\"log\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "\n", + "of_default = ObjectiveFunction(\n", + " cc,\n", + " lambda_=1,\n", + " pp=0.05,\n", + ")\n", + "\n", + "codes = [\n", + " gross_code,\n", + "]\n", + "\n", + "for best_x in codes:\n", + " code = cc.construct(best_x) \n", + "\n", + " t0 = time.time()\n", + " n, k, d = get_nkd(code)\n", + " t1 = time.time()\n", + " print(f\"QDistEvol time: {t1-t0}\")\n", + "\n", + " t2 = time.time()\n", + " ler = of_default.ler(code)\n", + " t3 = time.time()\n", + " print(f\"LER simulation time: {t3-t2}\")" ] } ], diff --git a/true_dist.py b/true_dist.py new file mode 100644 index 0000000..63ac51f --- /dev/null +++ b/true_dist.py @@ -0,0 +1,110 @@ +import pickle +from code_construction.code_construction import CodeConstructor, CSSCode +import codedistance + +def get_nkd(code: CSSCode): + res = codedistance.CSScodeDistance( + code.hx, + code.hz, + method="MIPDist", # exact distance finding algorithm + params={'solverType': 'CP_SAT'}, + seed=101, + ) + dist = res["d"] + return code.n, code.k, dist + + +cc = CodeConstructor(method="bb", para_dict={"l": 12, "g": 6}) +cc2 = CodeConstructor(method="gb", para_dict={"l":72}) + +gross_code = [ + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, +] + +files = [ + None, + "NewResults\handpicked\BO_results_d_0_0.5.pkl", + "NewResults\handpicked\BO_results_d_2_1.0.pkl", + "NewResults\handpicked\BO_results_d_6_0.5.pkl", + "NewResults\handpicked\BO_results_d_7_0.5.pkl", + "NewResults\handpicked\BO_results_d_9_0.5.pkl", + "NewResults\handpicked\GB_BO_results_0_0.5_4_72_10246.pkl", + "NewResults\handpicked\GB_BO_results_1_0.5_4_72_8595.pkl", + "NewResults\handpicked\GB_BO_results_4_0.5_6_72_16549.pkl", + "NewResults\handpicked\GB_BO_results_11_0.5_8_72_526394.pkl", +] + +pp_list = [ + 0.05, + 0.040036870145840404, + 0.032059019421497734, + 0.0256708559516296, + 0.020555614525359374, + 0.01645964939039528, + 0.013179856905786339, + 0.010553604389554513, + 0.008450665770303305, + 0.0067667641618306355, +] + +ler_results = [] + +for file in files: + if file is None: + code = cc.construct(gross_code) + best_x = gross_code + best_y = 0 + else: + with open(file, "rb") as f: + results = pickle.load(f) + best_x = results["best_x"] + best_y = results["best_y"] + + best_x = best_x.cpu() + + if "GB" in file: + code = cc2.construct(best_x) + else: + code = cc.construct(best_x) + + n, k, d = get_nkd(code) + + print(f"from {file}, score: {best_y:.5f}") + print(f"[[{n}, {k}, {d}]]") + print("-------------------------------") + + with open("distance_res.txt", 'a') as f: + f.write(f"\n[[{n}, {k}, {d}]], {best_y:.5f}\n") From b7524cf7b5cee73880b6cc354e2591a744b5a505 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Fri, 5 Jun 2026 17:27:12 +0100 Subject: [PATCH 36/36] uploaded best codes and updated notebook paths --- .../lorenzo_results/bb_dist_144_10_18.pkl | Bin 0 -> 2987 bytes .../lorenzo_results/bb_dist_144_4_18.pkl | Bin 0 -> 2987 bytes .../lorenzo_results/bb_dist_144_4_20.pkl | Bin 0 -> 2987 bytes .../lorenzo_results/gb_ler_144_14_9.pkl | Bin 0 -> 18269 bytes .../lorenzo_results/gb_ler_144_16_6.pkl | Bin 0 -> 18269 bytes .../lorenzo_results/gb_ler_144_30_8.pkl | Bin 0 -> 18269 bytes .../lorenzo_results/gb_ler_144_36_6.pkl | Bin 0 -> 18269 bytes read_results.ipynb | 10 ++--- true_dist.py | 35 +++--------------- 9 files changed, 11 insertions(+), 34 deletions(-) create mode 100644 data/BO_results/lorenzo_results/bb_dist_144_10_18.pkl create mode 100644 data/BO_results/lorenzo_results/bb_dist_144_4_18.pkl create mode 100644 data/BO_results/lorenzo_results/bb_dist_144_4_20.pkl create mode 100644 data/BO_results/lorenzo_results/gb_ler_144_14_9.pkl create mode 100644 data/BO_results/lorenzo_results/gb_ler_144_16_6.pkl create mode 100644 data/BO_results/lorenzo_results/gb_ler_144_30_8.pkl create mode 100644 data/BO_results/lorenzo_results/gb_ler_144_36_6.pkl diff --git a/data/BO_results/lorenzo_results/bb_dist_144_10_18.pkl b/data/BO_results/lorenzo_results/bb_dist_144_10_18.pkl new file mode 100644 index 0000000000000000000000000000000000000000..5851282e2cf94c4a5749a99e19bcb2dbd0599e55 GIT binary patch literal 2987 zcmdUvO=uit7{~Xk2_}sx1rLJAl7ns#LXvK5tClXc*2DTyE8+@<*{?CXGTq7S%r1?P zvLJy*_U7cE_OKUA5h-|z2WgjJLFfmBZ5oqCve`72)`RgVRq)++I=|Z4NYCzJ-)H{M z|9PJO^SsYYB{Wy-^~v9~9jSJuRnt;V+11^aUd-f%l#-R#OuM>QDXQsGUdt+$S}^sZ zQXaKG7yLelW{M*`SpR8z2y^ap&tO5n2|?XR=P{tSP7{`k{F zmB5>KgW`YM@I?}PMES|0Zs{3aQ_5=5%YWgZzsAxH8ddjoxknF4(6UN!+rLiuHSn;j_38e&)z(bd-le;&+hqO@m|0CynWc7 zu}|1>UwmN3j{9fq*=n!2dri$);_Ah^^}b!qszo(>G@r4=1`VFDEx~`Vol|x$6v=fT z3Hbj9YjsS#57tgwwQv9akrA<52fSP5-*KxR(#2MZ#^vwAH?fNkCOJ4bo9lPyv{x;s zv{I@)OeL3ZZ~BxyZbx3fw3OLy2mZ@gO!{IH*|`68O&Zs~m=>qtG~Yi}kw)uAI3|t8 z^Uu_zu@;>dr{F-ocloM=tS|iDl&)XyH!9MAjH~tOc1^lS*f`s85E3Hn=6b3w$%{+# zVQH+bt|X;_!j0CUxo)@0c+Hn+Y@O61OKcO0V`oefLm=X0kWCS8OhjCNMus8u{1S1M$NaP8IKoP?Y67o4cYAIBVniII7uFWB_8=Zr9< zGCC%9_vNm-By*J{Ts!}S6y2;| zPRb~X-adCc_Gq5va~0;yq!tF@AoNZx`eJ)=wMGv)wLqu^!o}gb ce(O?FDujcvd?oEkhzo@nEC5}2$!cln9~L#~)c^nh literal 0 HcmV?d00001 diff --git a/data/BO_results/lorenzo_results/bb_dist_144_4_18.pkl b/data/BO_results/lorenzo_results/bb_dist_144_4_18.pkl new file mode 100644 index 0000000000000000000000000000000000000000..ad86cc1d3f208d22daca6c0cc9ab0361186dda09 GIT binary patch literal 2987 zcmdT_O>7%w5MBQ?k({Qj%Ap0J%1RG7LW)e{C=F1>s@mpYD51S9h-m${jTddbUGF-= zND00~BFG1%ZVpfmMgjx~5pn7Ts1@~sDiBqZmbOW=iR;*K_^mhIF^sL0}@Aj6EOJJ&~RhrtW=}6vuU8 zJGv{@*F*f)=fOxb*6#Oz#T#nv_zi#Ky_MI%8}F_DcEA4(Z|4^TYsNZhMTAJtj1_Us zShIyLI(t=4m~`~CZe6crl5$2)9#s-1ZBXw?%Ow8hcIK>f`^`!A_RgkuH|Dfgo{Xv4SbdmMN~v#p&KkD@ zqZfZYFjo&8z4*(n4~uNvJhN$Be|Be{k=v`W5*y{QKW)cYSfDpWtuEYm5E#{C)o8P8 zW6>XGWaZ)2e6tJ4)n}eAvTM=zrOn3L!%vHB0I^Sv_!&X2pMx)z7=f$u)sJ>Fa_@`R z!;C=i&iNNh%^ajy4qYG*^;LEs46}h;C>8D0)}Jal*%q5yS%E2+%viF-&{8 zmgh1ch-1Q_P%|b5SEbuyb2yZt(~7`_!d7MvP&93({q3Tk4Y+U?$OS$j>5ok zE2~5296^MmFO<=0;CAC>;cTCRq{SAd9>_`9r literal 0 HcmV?d00001 diff --git a/data/BO_results/lorenzo_results/bb_dist_144_4_20.pkl b/data/BO_results/lorenzo_results/bb_dist_144_4_20.pkl new file mode 100644 index 0000000000000000000000000000000000000000..0bc0185743366c42e488a6ecaf91dea5c6274a0b GIT binary patch literal 2987 zcmdT_O>7fa5VjpBksJbu3m_q6r3V~UisHlsXo+I+mxEQB!l7HK(fV&2Z?*BVyU!Lz z3RpssNM#`;HU~-%KD1R;RlRa+R=HJGk$}(?8k)6})=4f+k1bT{^KAAriH$hpgZJ~y zH#6UxnYS+bXUkn4`b?RjQb$rUn0VPN?P98)%JhjjCM$+n+9&E#GAAo(kx5xY)y4dP zd7JaQ9h$)(i3!Op?G+U@krqGE)k!f~V3NUEr}p?fo}%xwfb!ytbN>WC{buZ7U(xs8 zdK>?q(mWwyFE4*dSDBhp6)`XAhOA~aFDD4y+@r{hDUv8<({duK`ECdSZe)c^l4!_R zBtvVP()=Nzog0mesxz%U<`dfZFFDJ!j+kHY*K?@Z39U2c&*gZ{kkDBtJCZd|ijELC zqvn!|bkVT}G_SCmlTJjAhWn2X9v=!14upp^pU}aLsa!hoPQTU`>p9Of{|7wdxaMg^ z_a^#!IKT1P9BJTI$o0SK*(#=PbNR3HyFB;#D!yyq$$Q=JopCPTeK!2&w0Xgdd15`& zX3RTn&Xl_N>=h}+_~`k%b-k~rC0$CNkyDIsQ11nkasJkJ7R-!4l<7R>^S%LVb&lT$ z)?%x|k%4ez=vaTGf9FfzaEPcs16L<1QzqQ16C%kT4Y(7pXW~= z_4{(du^_7Q{Hrp#mZEoU0;q+kUw41*a5fIlathD$$ASHE0*i0&DiW|Sjs&-Z+P&`` z7xFYR8yis~SC$+%YXh?r{&FJMug` za5hR7L?IynszWn+hy!9*5e0w|z=B#&X8#US^@^3Z2-rR6%LE`E06l~rB*gN=0%52Q z@kj^@WUPI7xk%N~Oe6#u7642T*9xv*Mz2OGA!NX{aqHtCxv<#K3;^=ux4yKA3=04< zBMMT{XIPqBhn+{l2FKlv+Syv1ssl7P9NUWvi|SB_65zlB05V<$$OEZ3Y4ezn2iww2 z#w!)Ccz?m#K8w}JOpq*S0(K0?Y0eB8>)-usQ#f7@SR@SYY2jL&IA{yT zgaaZ9)e*k-<4BpppA7#OB>)%lRN9v;a^W&UGtLeRt_=RID(+tib{@j8HmHRc$o)H0 qye5Am!3AMB1pqj(Ko8!=2uJ4;g;63LWLN-jit0iz$`wkvzW)LEYUz&v literal 0 HcmV?d00001 diff --git a/data/BO_results/lorenzo_results/gb_ler_144_14_9.pkl b/data/BO_results/lorenzo_results/gb_ler_144_14_9.pkl new file mode 100644 index 0000000000000000000000000000000000000000..ed953a642e5a7c01d85be3b3172531dffcc91639 GIT binary patch literal 18269 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 JM1#$j@(*IxB6|P; literal 0 HcmV?d00001 diff --git a/data/BO_results/lorenzo_results/gb_ler_144_36_6.pkl b/data/BO_results/lorenzo_results/gb_ler_144_36_6.pkl new file mode 100644 index 0000000000000000000000000000000000000000..befcdfe7fd1579f58bb57b94cf757013e2d99bcc GIT binary patch literal 18269 zcmeI4Yitx%6oC7pMJkFVXnZA0P-zuvTPy{%GKJcL712V>qST$GJJ`jw%kIurssSfN zRBAO678FGpR0QG^;|nA>8iNp(;44sK=bo(*9~d7|5e-rAoHMs38yZqm$K7W7!}HB| z@65U9aQ5d_%Ujb}pM`%*6UCkR3zV4ZTaxHJRSid*TS|R#H5iH|I*WW!WkEa`3iwo| zH5QKg+A0#O`TpUwEyjcY7A4U+!WRnr1HOgP@NK>Y?Wz*vyQUe{Sy@_6$FR`x^>x2{ z7Hzri>{2af);GEQze^)o#oiG-{o-g?4L65FzBVNq3x-=G*?fa{I9~|`RW+pel-5Ad z-x|re&pV7uwSBSTiv{mcVv*dXk-TE>5H4da;iwuJ(vaiLa*B)viNV` zJK1;L_4Me#K>zy=^!NU4m;SGF3{>8M`p(Jv=Tx55b2ydPsXPN+pE$RO1O5Hf?>Y4> z>JibYyl$Ss(E%|xA`XtK+$x;vfS4N*oyzOx85|uDb0gy5sLHLvsSb#_5z(o z0Wmis4vwnaDxB(om>UtD%IoGC932pIBjVtw%B{kw4v4u)jZWov^JI1hQs+iIXLeQY z>7D9;n3vS(RDL&4W_KWUZp3qDSLL4GsSb#FNsUhBck^U+2U6!oJZE-Q?&+QCfS8xm z=v00;PiA)@b#BCSW>@8&-l-1sH81`3PMz1ylU^O@@0|3tKfS7QPvcYv`kIISdZ*6o z=1H#(^mk7B+Mixkxu%VBaTiPqgF}w-ijv$Ma z;WhAow)1P?ym0(`W6zhn>{gbPtZX@5^r=W`^M~SotC_x*pmjIBJ<*gXu79g%Z4>Y} z>)&EmuhtNH+I^B{Fm@v#z(rmUnKegcGF8_Y1j9#b2rO`y4Lx%R z;HgR-m%5X$00Pw3NCb5wbp&>(gaD@c01e2xL{R&(jKI1&bb?*a83g%b2);aUAAz|C zbX-b~e~I9mx{VSVR{a1Vz*S>pgx-?PtMd&67(bFMC2GTN z`&Yo8Uo7Ji9Qh6qSVw_GX2d2Qc~X{K2ngNF?vN3Z*KN`e%)`qJ1jyYdBmDC_-9H=9 zm~WpaA;9G0HmrI;CKK3eo9gu*G^{Qz6EuBdoBhjmGHYJ6A=hu9G4ILqAS7?x3J9<} zL8iN-g8(8O1}&Yh=?Luoqd;(NjctB;-Gc_sFC+tZdu1|<8a)VXYLK8C&gFu)C24+U zAi&V8fy~@#GJ-j@Nhi>b8wl*Uje5+&G{yU)U@m z^!CU6Ynwsl`jI-p_OP+`MKUg#>k~Qxlofao*yc618TXMyX6+_2U%$JX3#-zxI}HT0 zwoF4{8)|s8mI|8;GL5DUx9$cqNA_w6y}u?lYL{(>KLfbLekk`Kz&&LKndj~Tg0|mf zf*l$GJn^JKrt>rj!MuN-M&|qdGD3IKNAUUfv$(KMc=IeW?B+K$G;GOM9Rcd|3D~~# PxZs=3pJ!U2#7qAMT8SG& literal 0 HcmV?d00001 diff --git a/read_results.ipynb b/read_results.ipynb index a2756aa..d8f7e37 100644 --- a/read_results.ipynb +++ b/read_results.ipynb @@ -9,7 +9,7 @@ "source": [ "import pickle\n", "\n", - "file_path = \"NewResults/GB_BO_results_6_1.0_6_72_33027.pkl\"" + "file_path = \"data/BO_results/lorenzo_results/GB_BO_results_0_0.5_4_72_10246.pkl\"" ] }, { @@ -203,10 +203,10 @@ "source": [ "files = [\n", " None,\n", - " \"NewResults\\handpicked\\GB_BO_results_0_0.5_4_72_10246.pkl\",\n", - " \"NewResults\\handpicked\\GB_BO_results_1_0.5_4_72_8595.pkl\",\n", - " \"NewResults\\handpicked\\GB_BO_results_4_0.5_6_72_16549.pkl\",\n", - " \"NewResults\\handpicked\\GB_BO_results_11_0.5_8_72_526394.pkl\",\n", + " \"data/BO_results/lorenzo_results/gb_ler_144_14_9.pkl.pkl\",\n", + " \"data/BO_results/lorenzo_results/gb_ler_144_16_6.pkl\",\n", + " \"data/BO_results/lorenzo_results/gb_ler_144_30_8.pkl\",\n", + " \"data/BO_results/lorenzo_results/gb_ler_144_36_6.pkl\",\n", "]\n", "\n", "ler_results = []\n", diff --git a/true_dist.py b/true_dist.py index 63ac51f..f11ace5 100644 --- a/true_dist.py +++ b/true_dist.py @@ -1,3 +1,4 @@ +import os import pickle from code_construction.code_construction import CodeConstructor, CSSCode import codedistance @@ -54,40 +55,16 @@ def get_nkd(code: CSSCode): 0.0, ] -files = [ - None, - "NewResults\handpicked\BO_results_d_0_0.5.pkl", - "NewResults\handpicked\BO_results_d_2_1.0.pkl", - "NewResults\handpicked\BO_results_d_6_0.5.pkl", - "NewResults\handpicked\BO_results_d_7_0.5.pkl", - "NewResults\handpicked\BO_results_d_9_0.5.pkl", - "NewResults\handpicked\GB_BO_results_0_0.5_4_72_10246.pkl", - "NewResults\handpicked\GB_BO_results_1_0.5_4_72_8595.pkl", - "NewResults\handpicked\GB_BO_results_4_0.5_6_72_16549.pkl", - "NewResults\handpicked\GB_BO_results_11_0.5_8_72_526394.pkl", -] - -pp_list = [ - 0.05, - 0.040036870145840404, - 0.032059019421497734, - 0.0256708559516296, - 0.020555614525359374, - 0.01645964939039528, - 0.013179856905786339, - 0.010553604389554513, - 0.008450665770303305, - 0.0067667641618306355, -] - -ler_results = [] +folder_path = "data/BO_results/lorenzo_results" -for file in files: - if file is None: +for file_name in [None] + os.listdir(folder_path): + if file_name is None: + file = "gross code" code = cc.construct(gross_code) best_x = gross_code best_y = 0 else: + file = os.path.join(folder_path, file_name) with open(file, "rb") as f: results = pickle.load(f) best_x = results["best_x"]