From 15749d124f3673cd8afd5e3e0a9297f0fb0f4fef Mon Sep 17 00:00:00 2001 From: ltcptgeneral <35508619+ltcptgeneral@users.noreply.github.com> Date: Mon, 9 Mar 2020 22:58:51 -0500 Subject: [PATCH] worked --- analysis-master/analysis.egg-info/PKG-INFO | 14 + analysis-master/analysis.egg-info/SOURCES.txt | 12 + .../analysis.egg-info/dependency_links.txt | 1 + .../analysis.egg-info/requires.txt | 6 + .../analysis.egg-info/top_level.txt | 1 + analysis-master/analysis/__init__.py | 0 .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 150 bytes .../__pycache__/analysis.cpython-36.pyc | Bin 0 -> 32633 bytes .../__pycache__/analysis.cpython-37.pyc | Bin 0 -> 26681 bytes .../__pycache__/regression.cpython-37.pyc | Bin 0 -> 6984 bytes .../__pycache__/titanlearn.cpython-37.pyc | Bin 0 -> 3058 bytes .../__pycache__/trueskill.cpython-37.pyc | Bin 0 -> 32168 bytes analysis-master/analysis/analysis.py | 790 +++++++++++++++ analysis-master/analysis/regression.py | 220 +++++ analysis-master/analysis/titanlearn.py | 122 +++ analysis-master/analysis/trueskill.py | 907 ++++++++++++++++++ analysis-master/analysis/visualization.py | 34 + analysis-master/build.sh | 1 + .../build/lib/analysis/__init__.py | 0 .../build/lib/analysis/analysis.py | 790 +++++++++++++++ .../build/lib/analysis/regression.py | 220 +++++ .../build/lib/analysis/titanlearn.py | 122 +++ .../build/lib/analysis/trueskill.py | 907 ++++++++++++++++++ .../build/lib/analysis/visualization.py | 34 + .../dist/analysis-1.0.0.8-py3-none-any.whl | Bin 0 -> 20459 bytes analysis-master/dist/analysis-1.0.0.8.tar.gz | Bin 0 -> 18782 bytes analysis-master/setup.py | 27 + data analysis/__pycache__/data.cpython-37.pyc | Bin 0 -> 4239 bytes .../__pycache__/superscript.cpython-37.pyc | Bin 0 -> 3965 bytes data analysis/config/competition.config | 1 + data analysis/config/database.config | 0 data analysis/config/stats.config | 14 + data analysis/data.py | 102 ++ data analysis/get_team_rankings.py | 59 ++ data analysis/superscript.py | 374 ++++++++ data analysis/visualize_pit.py | 59 ++ 36 files changed, 4817 insertions(+) create mode 100644 analysis-master/analysis.egg-info/PKG-INFO create mode 100644 analysis-master/analysis.egg-info/SOURCES.txt create mode 100644 analysis-master/analysis.egg-info/dependency_links.txt create mode 100644 analysis-master/analysis.egg-info/requires.txt create mode 100644 analysis-master/analysis.egg-info/top_level.txt create mode 100644 analysis-master/analysis/__init__.py create mode 100644 analysis-master/analysis/__pycache__/__init__.cpython-37.pyc create mode 100644 analysis-master/analysis/__pycache__/analysis.cpython-36.pyc create mode 100644 analysis-master/analysis/__pycache__/analysis.cpython-37.pyc create mode 100644 analysis-master/analysis/__pycache__/regression.cpython-37.pyc create mode 100644 analysis-master/analysis/__pycache__/titanlearn.cpython-37.pyc create mode 100644 analysis-master/analysis/__pycache__/trueskill.cpython-37.pyc create mode 100644 analysis-master/analysis/analysis.py create mode 100644 analysis-master/analysis/regression.py create mode 100644 analysis-master/analysis/titanlearn.py create mode 100644 analysis-master/analysis/trueskill.py create mode 100644 analysis-master/analysis/visualization.py create mode 100644 analysis-master/build.sh create mode 100644 analysis-master/build/lib/analysis/__init__.py create mode 100644 analysis-master/build/lib/analysis/analysis.py create mode 100644 analysis-master/build/lib/analysis/regression.py create mode 100644 analysis-master/build/lib/analysis/titanlearn.py create mode 100644 analysis-master/build/lib/analysis/trueskill.py create mode 100644 analysis-master/build/lib/analysis/visualization.py create mode 100644 analysis-master/dist/analysis-1.0.0.8-py3-none-any.whl create mode 100644 analysis-master/dist/analysis-1.0.0.8.tar.gz create mode 100644 analysis-master/setup.py create mode 100644 data analysis/__pycache__/data.cpython-37.pyc create mode 100644 data analysis/__pycache__/superscript.cpython-37.pyc create mode 100644 data analysis/config/competition.config create mode 100644 data analysis/config/database.config create mode 100644 data analysis/config/stats.config create mode 100644 data analysis/data.py create mode 100644 data analysis/get_team_rankings.py create mode 100644 data analysis/superscript.py create mode 100644 data analysis/visualize_pit.py diff --git a/analysis-master/analysis.egg-info/PKG-INFO b/analysis-master/analysis.egg-info/PKG-INFO new file mode 100644 index 00000000..cc62b061 --- /dev/null +++ b/analysis-master/analysis.egg-info/PKG-INFO @@ -0,0 +1,14 @@ +Metadata-Version: 2.1 +Name: analysis +Version: 1.0.0.8 +Summary: analysis package developed by Titan Scouting for The Red Alliance +Home-page: https://github.com/titanscout2022/tr2022-strategy +Author: The Titan Scouting Team +Author-email: titanscout2022@gmail.com +License: GNU General Public License v3.0 +Description: UNKNOWN +Platform: UNKNOWN +Classifier: Programming Language :: Python :: 3 +Classifier: Operating System :: OS Independent +Requires-Python: >=3.6 +Description-Content-Type: text/markdown diff --git a/analysis-master/analysis.egg-info/SOURCES.txt b/analysis-master/analysis.egg-info/SOURCES.txt new file mode 100644 index 00000000..b7f40198 --- /dev/null +++ b/analysis-master/analysis.egg-info/SOURCES.txt @@ -0,0 +1,12 @@ +setup.py +analysis/__init__.py +analysis/analysis.py +analysis/regression.py +analysis/titanlearn.py +analysis/trueskill.py +analysis/visualization.py +analysis.egg-info/PKG-INFO +analysis.egg-info/SOURCES.txt +analysis.egg-info/dependency_links.txt +analysis.egg-info/requires.txt +analysis.egg-info/top_level.txt \ No newline at end of file diff --git a/analysis-master/analysis.egg-info/dependency_links.txt b/analysis-master/analysis.egg-info/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/analysis-master/analysis.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/analysis-master/analysis.egg-info/requires.txt b/analysis-master/analysis.egg-info/requires.txt new file mode 100644 index 00000000..6868226f --- /dev/null +++ b/analysis-master/analysis.egg-info/requires.txt @@ -0,0 +1,6 @@ +numba +numpy +scipy +scikit-learn +six +matplotlib diff --git a/analysis-master/analysis.egg-info/top_level.txt b/analysis-master/analysis.egg-info/top_level.txt new file mode 100644 index 00000000..09ad3be3 --- /dev/null +++ b/analysis-master/analysis.egg-info/top_level.txt @@ -0,0 +1 @@ +analysis diff --git a/analysis-master/analysis/__init__.py b/analysis-master/analysis/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/analysis-master/analysis/__pycache__/__init__.cpython-37.pyc b/analysis-master/analysis/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa1abb0083cec6db1575fcf5da3f9955077f08b4 GIT binary patch literal 150 zcmZ?b<>g`kf~M@GI1v39M8E(ekl_Ht#VkM~g&~+hlhJP_LlHHsZ_ZI+yAOU`eI#LovNEAd--?TzeB1O6SfMt@AowKaV;dTKWc(^;x z9!L;5#C0J%O;W{9+c3uS;^TI=1ki;$=My`IEAIjrKt_WzRSLw z-}#h%)Xu+RqUJqP<{q>)V;AJDy}?`dy>?M@_Juje?2_b^(1ZKz5%geW!>a5@>bO0M z)F@K7gf-r8KOm(KggFn|4@u6gVa~($Ba%}Na~`!%NY249=S}uwl5;4``3C!>2#ae5@X|@+mKb>t8^onuu2yT!X0;k`>b__-x+lcQyrAMyc(y^Ii$A+}j zT3Mc}%`EADC}$b{YP3LKE3JCB0lbA3Jc{>YSiR_|meXvk0e3H)enL9??Cfl;d<=aW z%@|X+e5KJ{ET4bzd3kBJ*0}6ES-TpcXSOv2R=!audXJXfl{twyh?6km@NSEVM}}A4 z2Sc}kA*|Pkg0j};)BUcB0IRHvqD%TCu(?sWi*4O75Lm+>Ad^SNmVmuZo=2`O73;6cC~V|op9r(O%Y z#Ue54ggjlTgES*DbyIT}mz`S6#c;1c$SIP2WT=UG)*3^Wi|U$WZo8~BGjjyzu9h*B zczveZK4LqU8+Atycu+?SNj~uH=G9hvxdDVnYQos{Q%9!Xw3&||07yfs>#7yUz1V0r zBXo2BW}LJzs4>umiA02j-8mWop{fybWST|#kd+sjjf?H${!`5_Eb+8thl4@@V(1G9 z7q8dxeo$?9MYYP^l}^*a+<>8|*`(>3QnfzavaTWh_G^kmLV=(k#261$sfVuLe63zz zQMG!%nd4|CMrxX*F`q{e%R(_?(?i-Xq$ys~<;9v?7NMv4QtAsa#F}ClQs_MAOX8Yy z1<6a3aO|BJCK{0S3rZ%p{t(S{u9neqKSPgFga;t(+n3|i5NRpOv=9U}OrEv!d|Pc- zqsCRNbPW>as@ouwL2ieAfVwo;ZoS=1g9_2OTXt6KPN!S$gzOo@%n3B6Kdp4MMp4C-*{y26k5CEA$8@Ox z;VZ7w1X$&+x>}|R$f-)E6c7OgC+cwQ27lcy zI)Ai$xu%e;>zR_Gk2S{l#BJ3&9aMP4@6t3TzooWnbfE*!L)JMCX{jZltsHK167@-uh`V)IAuaf!X!zNT_ZZd znk>^00Tl0UZ1@LQRrAlhYA=`Fi%q8nDW+jB-=*cH;4kdgl}6L9*HpZN0iI&y8;p1z zblb(e3@xV>iMQuKHg(X@QH>GkPcCM9fDu(Q9>aa zz##)%U8{=18^JVSsI^e}N~gRCqorL9*&P^dv@o$k1wqS{MX0V1L%Y*$EH~Cfzlsj@ zRsay-h$stTr!6sGy9|m1{midmB5}%~RglYK*>A(q+P22VcJ;T~EpR0fjybvC0tk(^ zO3<^Tpqo~Gak-{0lJHfdQ>AE(DtJM|c6qJZBKJ{A_B8~bPG*4Tj%wE_omvYsVW|ji zH5G%Nnj#;{THU@k0E{LSFduX_f}EiFz{)r_vq_y+&OD?k%oRJ*po|l$bTChX-2qqR zVV@FUvc<5jhc^H#5b?)msAk8+7;H$46X%F{p_teT+QD4oTmk1mR>G^|bfCln?O+6f ziK2z|$HbK{@v4_=%@ruVz5x>}9}`BQ_|}FWBmidIdKVLnh=W#_CZ5(@0z z2&Er^mjz;nPLzmrh_d45s%ihk$Wm7w<@N1%qR&UHY6z}@(|Q&9p_*@WkCv}2HXu=%x6;Jy0Nv$E zn@WHSf$OsFu%x3DxSY~HqI*QBMx#5Zi8HYODgh2eS7eYwvY{lF}a>NQh5!PbE1{X&Rg--}7=937D!ajw#qLCP*AdU5immp@E2a-^PM_8No z(tyQeG@aEB=0`m__F1q`Mr<<1)w)T(K3LpWOvKLFuG(lh)D!J7=?kw$voM}f?T2ET zHe<{ys$m-FCJA9h0k;ZMKg0+aRD{t0Y?FYyP_5vG-E=r#b*@JSIbf56Xk?|^SYht^RN*$RvyRjo=q z^chE~wQoH}h(7|8u2!{Sh0u~JyO&mKoTaxMcQw370P{8zN))XAki^g(lN7@ct--y) zLDUUKILQ_xSLSJ#UI`FdFic3sAc+YO-y*Zi_t{6mna!2umiF~3I0HnVk&3B}l6I-3 z`x!Ab1~?ircMKt%_z>{j<1?$PtD8OaYh^fCVQtXO9`@nB!Lq}Mmz5qHET~c3Y}af~ ziFNn#CTfXdVE_a9*UL{pfUT(Vb1UV?Xoxbg&_1;QcYbpQh&=iFt6&ls1Wh81f>c%yn5A>Bcp#RL@SEUIKmx{Frx5 zo2rPsIv)fd6OmK+x_sgoSid%~er;mS(DG8ECCyGJylk~fpKP`2<*QY=S5}&g7pqlB zgr6%xGu%)N_HGVX_*mYZ5V3AHd~f$ts6Z9ok`5D zrRytp?Zh!JQSYpvODQqGy(DwJV#BT0E`ti`X92F#dIszFNF74mDuEzJP?Crk=D1mB z+x+lJ0bhaR5k$IZ*EVhIRqK6+*3B+p>L$9$rBn|K-~3y*dgfAk1HiuxTL<21oHO2T z^elnYGXO!RRO?9*Ko9^c1H`B9viC(go>ZN;``80ZN&Q9mm-Kbd|MNmoC z+%DF5ywp6Hyz3=71$k-h%=1#>>+!OIGtW!4I<0q+8bC&R`EW=+Ug*dQUC{gDGYZE5hF2XWzF!1V+MIN-QM-1{nHPaKfdrw`p#$AQmdg+G@j{PW3VFWqh}fSBm+4 zD!I_SoKDEtc*&YtaJ>Y=L69?m<*tyT5_vOkPQ<|`o&-lP#lU6vJQyK zU9o{wp4lDjIQip#cASFLNaoGm z==zwMw;E%28pgu6Onm(5&#%4mcs<9*_>ADocLWvlAe;qbJ=Zlhe6GT_9kZ9Xm{#8* zc^0}0`9aRvFuSQG;jN_%Vp(uthA5I;YXQ&W`Q$JgXnQrip6}(Ba=p9_1TE$9o7w}} zgTLZ?fIQ~TTN@@?PP66rn<^Q$UAU39-$2WyftE9V%ikKdT>Lt#_AxcVuaFX{=YW^RJrgd8BqMjraJwb7^A3*sy@< z{L-$T*|Rn*5a{k+o_Kw~dFh8C?F-V9rRKsuPJW&GI2!L`F9)U=o2?`;v0T7hk#E2! zpq<-ZNfrj6)m1q&dnM&?xwDE{#dTa^9CZ@Z>Lp;}LR(txR1%%$)oT=ZdcMTMm{Uo( zjfHD}gpYa#5BkCMl$zq(dAO$hB`7a@{^>IpxIx7meNGHas4&mUW({?UFW-)!lI}XN zb(LpfwkC^uX)evQY%kwm`uB>@wHLydz2Z}=oqj3>jcLJACs^Z~SmXE$;hNc5r$$%2 zTgi4B9qbWlIhE2f{EgKxS!8BSQRQ)(m9Y>`yu>w6F9Elcm+D|qzUxh$Z!9dgv3;i* zwyxFP=@A79fW9M0SP~W zry$3~tkBi9JgUL$5>EeK!d+ROPJ1KJ=oS1H3%*7_+U{U^F-&9ZV5mC^>wlg(>zU71 zvY<`)q8xh=3v6M9Sl|R?Bo0`0_JbeJSz&)pAwlJh%!_F=v7YXwm(V*q@d|o%h9W`7 zlZ>M;3DZCc3*Q93$$8H7Nh-~0@>XlXvy|;6F1MTE-e37eUennRwdGq4e0Vtg4tl|qPiWmk$}0=-aGc^(b}I#<*~;7gLUU-3cKDZwGe zs2%qRsbD=ZVjZ&T1ezaa@hb$?MFcX~TAk||khYce1X9V>vqb(oPlB9MwsA3`&Iq}s zL@7ww7G$-BXGu+7rg3SC&o@YZkv!->7D^;3jW6ZjMy)Bqq2AD%cVEs;C%xQ-V6#dk zeNIMFy#=6Ek`$2B=}I0uK$cxm4OD{pEQ@@5ihLsob&3w?aV;?RU2BJ32tqVn~vHQU0@-GrTxoxHO580i%EALBP|Q|#we z3Gaob->Ag?M0Hb5rLZw=Qnp8jA2u3ekq!XU&T3YF{$yh|kcwtygNgXTR@m4Z=)^)sO5 z5)@vUA_=2fL2zB5S}639?STSO%tD|XV4b%zC^JwDh-$@tDb^Di2pN(?%p<*iFB_ni zoVBt^{tf2_stVWV26Q^BNHj4_g@S)So@~VhVom^KkjC|8d==2sM)|GBjJ^Dk2zqWA z>NR@f1TNH684LGk!fxgM%GT;+!XN3Y*GEgsP0!H)iZtb)x%#j9D4fkJ{_r^>1&imujek_Lpko= zeYc_)0oKd*V3_v` z&^Qb0#a@wW36&JYwEv@oSZaNwH_|JKFoFxG;N|I7mR-TFm*i@jmu`uF+{?m^B^HF2 z=(d~Q1my!7B{j z!{EIPzKy}3W$^6`z5{`m)|-k%*rIJ+=^zsl&7a|5k%NuIA*fM#Gi{xHjY)p$965ey z4)H4k!l`4}fM6gp>)Bpb4+JCu5T5ahKseASOUyty5#^<^>i||NY}?CrRkqS8>6AC3 zO*+S}cG_1k)3a;SNsYl076@38D+}qB1db+DN-!p?aE!NHZibxBX`Fo*-cs*l@O}p0 z%|LMW=NS`p{W-=ChJnZ=Iv{_O=tv@BB=%ab-7lYC19{?{K*P(}*j=^K?1oklX*5&` zUqr%3izt8M%1*wNTu-k9Ep$|gYf&7ya4jXo;Xzk!)^~ousSB@N0nXn-FNeG$sJkeX zo75H`=29rf@U6L6=+(G^URej7!OI5IIxD;QknnP(Wvq{O7cqZROYugC zn4EN}>iT%x1?o5b5xdX$XB93W70h%P3ev0kP6nJ9rb~iqFAev<({$Ab_~3gPd>?}k zBJlFslPvyYpyt3V$gz7*EyWv||EiXmPF< zu_VsY;%qHqDV(Xr`C7!_>V~kMP!o7H6nV3+ea~kPf2lCD<76o9$aW#?ynuv1%rYx) zA?>hOg`w5XU0>FLf%0G=k#%qY`_e8h=OROEd1*Kc!7g42N>kNc#@-5UVcj&6AaQz$Mh_uCtdVp-(BwYEEg5jMR78N6oolqPc#6q8M8y_3E<6dhejA5k z8k;dZvNeYPE|DA7eRF8e%>_+PMUf2GHe~(ULDn*5YYs zkb2wwP2shV(w`-UZ;E{L)LM0$2ZtbbR|K*Bk}&>1j+SQCk0OKy%!K+$1Q+0g>ZjCC zZ_2tbdv>z}=_mO5CmH+pk3SV~?K`_za z_&i+vI6jZHyGjBnajV)%q>@PCVh21+BNcJ5;z9$Xl99>Cz(oxuOhF@R7b=Z42Uv7- zge+)IH%9L*UP`WwVFS?2Y1nk_WxW_yr(wenfB6-~RPv_6|&TnB#UR`Y)V)?Q0+X>aRXO_rl}X*t)i(yhQu5x-OBx?;Gs3*^)^5 zYp)hpKjobj8Wk&*a#9uI&5mHZ1-6{C^_GEU*^oW$RUnd0;Bv-+T=;34;i4d!Q&VreLQk&n8sp4&CSC@A?Tx2*^D_$Q=EvLA!PUFpc}x~f#DFn5kw`>le0n9 zz|g+}Y&VS|5oiM}>F+VQDJ)@MpWc}gVCqxbVd_`$aM;W`8*~SlIy3}RK4fnMTN(e| z=Yl4HvCjgGA&ZCPT%k8Xsg#NUD+7E8MZA$X-X3AiS`8dY^H2S2l&X`y_~CB?U!k=k zBG{XYodUf?u&8W`*^Mcoi-%fu$!nLRY%?wCc(AW$fX*)9opV9gfV~W{2O~9Xp5h{G zo)Snm4|6Qp<+L4wVMTnFsXi>eBbA_d*n|L|Y5+-x;Ksr6b~-qE8g44rt8_0CJi!L` z1g%XHz#*mHFu>w+iU6$s4dME?42D5CDs8K9H^56V=yrzC^-IjcTRx!CQA3jHSB6@< zO+4oPI)gq=LYfhM{Vp~|3dmYQD0{K!oc5AeaA;okDb+bV{OrY-?pGn1B}9B}Jnk)@ zSl_k2dwmkmxDhwp?o6|r4p^+_jKbx*qc`5$38Q>;eX2JF_s2vJH+{hNP*dyJfsQ4l+#Fn4+}7Xma_#n<-g2SWa70(9 z{_f?0$7SB9h3y8vh>T5!t(V1iKW7=6@GHB|$?3DRat2~+kMiC@kMh4mz5U*+f6wG^ zGWZ+x&WRma?wVh=$8ba1;H{O^d zedQ4`;8?p$yb-buqE5dkKYQ@KK3(lx7s?u-D}N19TYmv%30+P0CP7y_K~1EqNzl~< z=xVAr3A&mhwQcFK996>*`Z++UXqfa-sJ_JHwm91~-0F*Xr?M+L(p!40_vnG{d#s1S z6%O>x0~Z;~zsa8bEe2bTFKMNS)NcA8Y3Z@YA>AU*!_*v4zxGEv=i4f;v!1>mieT8z`hk`>qI2~ zmidHS9cfl}*@vKi-7b52*K+!#7_K>}{g`PO;VH9C7q!IC>;-Q{kM%vFFX7{Xwf^>A zp2WkZ%KCvX&WxD#HhoM?gXdMu#*_a8C$$cfmgb?;aN5` zC=llPD>eGy-BWAw)X&xOWvqT;VV+mp9q}&&!t!7QHRb+c-D&B->2Eyq#67`5aeX<% zb&uR#|I{8_?s88DT!3yem1m5`OaZ+#P zgA$DlhyiWLs&jnfMF!9DX+isAE^xgyIMStV!;?@YP9Om~)H5@6z8(%G+1zVv&dR(G zBY|pMCC?p*{z0!hkr7b}4lzkse~3JeFREONY`dH3?_-a#JWHO!*FAzDVEF~^&E#qo z*QsRhFf4bjWzl6w!#TaS*B5)3b$zQlM8Is=F;*U0mJcC8m7|BP*Ik@*dlef-stM-6 zU)j&W<`M2-SV}Hn7iSeaXVF}8_5v2)vBUq;N(1{XtGJZQmL2M%;b4!)?Wo(sHrJM) z&6P+mDJJI&h{B8@{=6*57Ry+JhA2-9lJ=|Bj{{`A-xtf<VGohbp``n zB1QaMjM(njzC!`M6|X~Y!U0yRrR`?{ZF>^@9YFF4LPDJ;AVKU?-(o*5-TuRf5E3wr zZ^nUQvy=xh7JOnX`oviBi7_fDHWQ;hRt$dtQJ!GA1-e9)tGZqm7P=NPH$vCHOw^0jAaYb>>CtUTL8E9h zg;Q*Wa|owabZe+kMSfz4unp1Cg*-tSp6L~#yJ1e?cuY+A=G6Z}1FFvhf5hY;G8o1U zzH^1#fNfP2!U5h&k_s_4giAh61f}sNMKXl)-8X`qfLlhk<`(IIKrFBe|9*`f_$-4P zV4N3G?iUE$7$O1VP=14X(!*5;%AM_)$7h+W&oVimWhNtCj-H46lfovNSo*lW5x)5K zA$(EZ0uNs&Uf`))eE$!4L5S)s>8U?6M0i0_nN`>dJal^a`~czKx>fH3V$a_&v2Q;k zP*wj78|Ok$l(4^!WF0-&`TsLn|MqprI*Nz?x5@hHH%QjMyA4^#djH2w)|?W*cr#>u z1}|(Wt+l3|@g;SZ{}Kd7Urx3!!v5sH2BAMkBzSRcD#E)l8+bNcT#QZMZ${Htq%f+S z#9Xz@-m8nt(~)C`>U&jKUy?dX3g+`1ZE?%)jIS{$~avrsA4SL^1aTmMG?Y z2X{umIMg=(z=Y@e3==07>skf4z--NUjcz~xa*Y*Av@CScHuJ{kkt6o>;m-E0j) z$^N8cP-0DQODIF)ZulouDt#ES`NOT}Vo*GRoC19rGW(nL2k?i8oH$o`U zT-I7Wf*9_47<_^gzD1tc?WG>Yncqv#@Vu^_MAzqgI2Mg}vUq0T4hxQ1l4HgjD|{5B z#IIqh$`~DOJb2cu)#X;&$cW~Jki8;yr*Z$H+ zsGj4uiDST7p1daj9 zK!+E13j6}0JB4OY4$ty9C5@x{F^#^Ur2vs4XQ2ftO87m6Rh|09L94f~(P+$xty{sl z8Jw~pvMRjhs0z2smiF&1qK;OlVc~uP==S`sMU}ib;(`g`dM2<4Hc@dzx(SDKsJ3>) z{1-g^J+P)bj!0kQ$)>SHn9AlGUjn2H-~T~=e){qLW>Rk1E8r`vv3gI^QlrT6Y8 zv80Gq02~u5VEjgw3b=~|qmNioN@kZz@f)WuJpv9hdxZrEmeq4%OJ^f39luUXTi5+z z)*Xr`>p)}<)o#p>wbTPQY=XE!T>R7v6IC(DPF&(d6dJi@=O*O6cK>7`s2=F)oX^l;Vd%p zGU)gZX@(kQFvdV6_YTI!860HY5*%XeD+nIu@X`%!-1y+`jtp*O-2&&x)iu`ppAby% z9xwoBpn03z93awM5B%w*CshyA5f6Ul61q&QL< z86Vk!zdNV))bU>U0V<7C_`2VPAkd!S7{tn`jcZo0RHA*;DJShDofOt5GrsPe^>ybQ zo@5=+6}0HGjeoB9iU~a$|6mVq7x_>2@LTfNOGo(6_K+UsKitFb82{-Wes^%K6Tjp3 zE%pKY?zC@(C;Xt?lCl86@cG>1vE7eAfR^K&%P~h{)|`Nj>0e~C@W6+E^5NhAgmbF# z88XWJL9F z3UE-rtp@6#01<+Orl(8*rI&8>r7weEK;UXHUK|2O_;5Hv=$s4?0wh|qk(NQwAs%=P zeHac>?56#WNW;ZQ|Lx&@mr+39BmIswtoB8^TnB^^(msSFZV_awFAr%|9$e!%UV9mp zy!Och@A>juKl$;;>q)%F4}Zf!IDiCuPbCaO&^I>_g9=s+)*fLy>_t`T76vxn2)2{W zA>O}%X8=Z78=~T^Eec!^lQ-8!ecXg=W~fp=`~}RKNVx_{WdmPP2r(V-jwZ%AHGphs z8dVcwKVad4_9V_@T*i;Of&f>Uwyy{dHMqk^u88ARj;5P)XBr_Yt+ky2kdfyh`ZB0L zi|=WC-A^OX#~^rfA?7EpVd4Zv&yo}Yrj|nUmRhTry4*~)L3g}#jdPWq)s(jWVC<&XQ>#yQv-%cFnJtgwiYJ=Gc^K1aS}UjyK$Q_H9p1)-VQkW)w}q!;QnE+whl_cX@)o^0HeMFQ6M zK22M3t`%AU&pLQVQQsdV4vMLEfOA~`bi~Jb?24yhmq87OZ6biJs9_7?GDCL0A&@$K zki?D_(h?&KM^UWmWlUYr`(MOmQse`5h#V1;ojS-QSyvHZ>J$RJgnP8APiY~R5fS1Q zv{bFiZK#=ZXLN+;P+^ogI%T3Q-oA8xWqGJ|wus7?3GHq~3@B$K7Tpn9>oClXaVTxK zn`;xpkXk33@k1dLvfNofZJV%QeTvmox}KK%U#anv^+3mIsyBX{mXL}laAL+!&LK?2n(>l-Zc=x!%ZCx*aGlJ?!%%W}fS18u^RklL+*~UKJrpDkcNM*)JD6CVjdk#(h;=!lB*K;#Z}Kv?M7Xm93_!swB`%@PU`>9Hmhp%G%i_volT0>W-rQWP9d4fg&vy!9dW zo+^*-{RDP$uH}OMUk>|@QhhfWRdY;ooQ=^BFh7P<7ES4@k^2zI+R_XE>IF^WNewXx zSdW|NasC#jZ0EQZf|X?F__;HKuyTk$LRdNb7}i7t%BNx~70K{4zGA=nFak=^7BCr8 zU@(p^JjoORdP3D0KZzIhsVbr!T1{M(SFBFM*E=iLW*9jNA0v#MC9yj`#N$FZN1+7f zcOOFff+${P%IBElS}s$jkS$cpQM+N|A6(%cbsSyA|0(%+q_N%ppbZq^Epi9YqPmqq z83Aq-ppqB4#_S$G+QWcaotMS=Nt}Dc;Ym^F_Va;gdUd`gjwYGr7MY~nP-G)TYSMHm zJb8@CgDlby4l%h0!CMG;8Ii1&w6ck8BJYQJx}e6hli5r*3s+PU*hW2uB1U0Le+%P1 z2IN1r&fr@ayo&+(W%k0XB&IX7I1eY;uLcEp)aoxV@fR6<4@+aSz2r#2A7u82nLVri z8soxye;qMwdy)*sb)`~Pp6eHI4aINpA*V#+mlatn>V4g3K|#J2Msa7dY|Is!iO|tOJ|Ho9*~j!1{wE2A`vC;WcNx!Y zXVDL{@+~u+ebt#$wa literal 0 HcmV?d00001 diff --git a/analysis-master/analysis/__pycache__/analysis.cpython-37.pyc b/analysis-master/analysis/__pycache__/analysis.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9d52e8ee9a5a977a701ce6545d86f8bcda85344 GIT binary patch literal 26681 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⋘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 Oy)oZ7jNiiI@qYn3mzW#? literal 0 HcmV?d00001 diff --git a/analysis-master/analysis/__pycache__/titanlearn.cpython-37.pyc b/analysis-master/analysis/__pycache__/titanlearn.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20d728b4f689d96a58a67d5efa62afb6878a2386 GIT binary patch literal 3058 zcmbUj%WfRUvAgHFv%BPyVk|pxl1X4ASVSf%#)ttz7A#wUfI#6z1`G@$42ILa%UR9N zth#$7YB8HbD8MHJ{si*qKk&EoC5N1R$p;uYq^kEpnn7|%54yUhs;8@}>ouSE`w@Zh z-Cw_d{|G<~^Z~^%YrfLCjk`PW4%2~p=!`(4U9PV+ybQ2fWp!BAG;!lGlpoE9q zc}tS;3E>?cy(K&n?xZt`E}Wzbv@Y)ftp~K;q(9jL>NdZ_xA^v3nhf}WyYTG}-{HHk z?sESb8Sc$HNBf8P9NwS*JwMB;u_){DakQZxM=^|#;*4{_!PmIX4~O< zTV79RrO+al;3aejdLPQrY8fic$rh6l<9kRZ)%O zvaV;*=9`tgrB-8637MfraXpG3ot!+o7oTRT$m3^E9&b*F<_{l9eb&hMX%j!lELt|7 zXA@l~R)*Bj^gALu zfdBa9;H;jCgRE>0WPMtL_Ub^(qr;=4j}$1T#rVwu&vX`NRaU-Hg*w*FL;Y8&FAT*>A+h~sz2LTNA0RTZDLP^5DCL_whg0~?=DMTwolKVArN#cPGo^eiu z&QX0FxZ)u=)h+-{G;PTRyJT0uv}V(jp<_JN%!HI(*kZyo1%K-_1*2Uh$`P!cG|kIQ zsWi1$AThIKAHY4B3U8Uvc@ISIuRqQ$5$L$09$&yzc$X`w_8_?9Eg%`MsAi2`o{e{G z1(;w|%h#FY2l(kqNEd~y=tCcsMW?5!iPCgh^QJ_+m!>b9tXy)eo^l7p^jBu&qAb^@ zptvEG+iR)z5`K>oQQ468981aJWi#+TaVRwZe>n!r2sp^I4#IQ~On{-GZaE2!vrc*k z5OmUnO`M5)K`*_Vj`AfGGPo^W%lyoEjV?-MTs+VOkC(?9$=?8;+bRPHra}nQDt!c(-Z~R< zNw3Jl=X4Rk498^A`Iep!G;5($J|^GN=WG$3|4Tcq!|4TkOrHPqFKp3W^x7V$?~zNl zC0BIOhc~u)+iKlq_`+56q0DZ#Uc23HUAo$z1Xpm%V$cpQLkw`(*|BTD*(YQXv>|Y! zD*|}fb}qYZctt7Ey$jf@``l^CU2=hxK6mw2+r6N73HR<2y`{HXAM6?Y0{E8d11CUE z8(r^0t2QTuz#BjdzovA661D-JvE23YWw0j~kUw^=mo%c5_vaXKf z`QA*{V=0t6jv)zFZShBXFn8iMHlCQ(`PqCgZ=@8JhBgU70lgKs&wH||R=rlK`OXLm zu{wiJy%sRsSq1?{uJI)PESQdf5JEA+FkL5|xsY`_mKnFfWV{iCgHFQJEPsK;6l%H_ zGvi`@8b6!OM8$0x=m2_)TBDq@l*5mM8Vv@yqN~ zlqw18MnkhuiHo1#N&tzK^bt(84}h?Uy7;hNw#T;U77JNOcVUJuRCXFspY`bHbeFyl zv`vCwas)7Kf;n;%gm)XX ztfd#&0-Te%pEo=^K1`f^)(rh+(qofLTx2A@LZ#WOtN?>cA%6!9Yt}H7hlpbVmw!Nj zX)W&~uvGz%kXW#8U5bg6=GI?Aj)1MOixBoM+zvi2lYfNwA^mcACxPO<`WZ~q1pDRs zk965$vqqoQ5^$)!nQfw2u?(w~{ZCr{F=0|9tf~_K&$c7m26|o9jALs6y3A^D oL+wq=GU0<|TYrfE912UL+jAY)W7Ky-I$)u{7rfJr-0;JH13vV&rvLx| literal 0 HcmV?d00001 diff --git a/analysis-master/analysis/__pycache__/trueskill.cpython-37.pyc b/analysis-master/analysis/__pycache__/trueskill.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15c7554df5d4e48dc76a35fea45586d99213951b GIT binary patch literal 32168 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 NJm;X^XUh2c{{ue", + "Jacob Levine ", +) + +__all__ = [ + 'load_csv', + 'basic_stats', + 'z_score', + 'z_normalize', + 'histo_analysis', + 'regression', + 'elo', + 'glicko2', + 'trueskill', + 'RegressionMetrics', + 'ClassificationMetrics', + 'kmeans', + 'pca', + 'decisiontree', + 'knn_classifier', + 'knn_regressor', + 'NaiveBayes', + 'SVM', + 'random_forest_classifier', + 'random_forest_regressor', + 'Glicko2', + # all statistics functions left out due to integration in other functions +] + +# now back to your regularly scheduled programming: + +# imports (now in alphabetical order! v 1.0.3.006): + +import csv +import numba +from numba import jit +import numpy as np +import scipy +from scipy import * +import sklearn +from sklearn import * +from analysis import trueskill as Trueskill + +class error(ValueError): + pass + +def load_csv(filepath): + with open(filepath, newline='') as csvfile: + file_array = np.array(list(csv.reader(csvfile))) + csvfile.close() + return file_array + +# expects 1d array +@jit(forceobj=True) +def basic_stats(data): + + data_t = np.array(data).astype(float) + + _mean = mean(data_t) + _median = median(data_t) + _stdev = stdev(data_t) + _variance = variance(data_t) + _min = npmin(data_t) + _max = npmax(data_t) + + return _mean, _median, _stdev, _variance, _min, _max + +# returns z score with inputs of point, mean and standard deviation of spread +@jit(forceobj=True) +def z_score(point, mean, stdev): + score = (point - mean) / stdev + + return score + +# expects 2d array, normalizes across all axes +@jit(forceobj=True) +def z_normalize(array, *args): + + array = np.array(array) + for arg in args: + array = sklearn.preprocessing.normalize(array, axis = arg) + + return array + +@jit(forceobj=True) +# expects 2d array of [x,y] +def histo_analysis(hist_data): + + if(len(hist_data[0]) > 2): + + hist_data = np.array(hist_data) + derivative = np.array(len(hist_data) - 1, dtype = float) + t = np.diff(hist_data) + derivative = t[1] / t[0] + np.sort(derivative) + + return basic_stats(derivative)[0], basic_stats(derivative)[3] + + else: + + return None + +def regression(inputs, outputs, args): # inputs, outputs expects N-D array + + X = np.array(inputs) + y = np.array(outputs) + + regressions = [] + + if 'lin' in args: # formula: ax + b + + try: + + def func(x, a, b): + + return a * x + b + + popt, pcov = scipy.optimize.curve_fit(func, X, y) + + regressions.append((popt.flatten().tolist(), None)) + + except Exception as e: + + pass + + if 'log' in args: # formula: a log (b(x + c)) + d + + try: + + def func(x, a, b, c, d): + + return a * np.log(b*(x + c)) + d + + popt, pcov = scipy.optimize.curve_fit(func, X, y) + + regressions.append((popt.flatten().tolist(), None)) + + except Exception as e: + + pass + + if 'exp' in args: # formula: a e ^ (b(x + c)) + d + + try: + + def func(x, a, b, c, d): + + return a * np.exp(b*(x + c)) + d + + popt, pcov = scipy.optimize.curve_fit(func, X, y) + + regressions.append((popt.flatten().tolist(), None)) + + except Exception as e: + + pass + + if 'ply' in args: # formula: a + bx^1 + cx^2 + dx^3 + ... + + inputs = np.array([inputs]) + outputs = np.array([outputs]) + + plys = [] + limit = len(outputs[0]) + + for i in range(2, limit): + + model = sklearn.preprocessing.PolynomialFeatures(degree = i) + model = sklearn.pipeline.make_pipeline(model, sklearn.linear_model.LinearRegression()) + model = model.fit(np.rot90(inputs), np.rot90(outputs)) + + params = model.steps[1][1].intercept_.tolist() + params = np.append(params, model.steps[1][1].coef_[0].tolist()[1::]) + params.flatten() + params = params.tolist() + + plys.append(params) + + regressions.append(plys) + + if 'sig' in args: # formula: a tanh (b(x + c)) + d + + try: + + def func(x, a, b, c, d): + + return a * np.tanh(b*(x + c)) + d + + popt, pcov = scipy.optimize.curve_fit(func, X, y) + + regressions.append((popt.flatten().tolist(), None)) + + except Exception as e: + + pass + + return regressions + +def elo(starting_score, opposing_score, observed, N, K): + + expected = 1/(1+10**((np.array(opposing_score) - starting_score)/N)) + + return starting_score + K*(np.sum(observed) - np.sum(expected)) + +def glicko2(starting_score, starting_rd, starting_vol, opposing_score, opposing_rd, observations): + + player = Glicko2(rating = starting_score, rd = starting_rd, vol = starting_vol) + + player.update_player([x for x in opposing_score], [x for x in opposing_rd], observations) + + return (player.rating, player.rd, player.vol) + +def trueskill(teams_data, observations): # teams_data is array of array of tuples ie. [[(mu, sigma), (mu, sigma), (mu, sigma)], [(mu, sigma), (mu, sigma), (mu, sigma)]] + + team_ratings = [] + + for team in teams_data: + team_temp = [] + for player in team: + player = Trueskill.Rating(player[0], player[1]) + team_temp.append(player) + team_ratings.append(team_temp) + + return Trueskill.rate(teams_data, observations) + +class RegressionMetrics(): + + def __new__(cls, predictions, targets): + + return cls.r_squared(cls, predictions, targets), cls.mse(cls, predictions, targets), cls.rms(cls, predictions, targets) + + def r_squared(self, predictions, targets): # assumes equal size inputs + + return sklearn.metrics.r2_score(targets, predictions) + + def mse(self, predictions, targets): + + return sklearn.metrics.mean_squared_error(targets, predictions) + + def rms(self, predictions, targets): + + return math.sqrt(sklearn.metrics.mean_squared_error(targets, predictions)) + +class ClassificationMetrics(): + + def __new__(cls, predictions, targets): + + return cls.cm(cls, predictions, targets), cls.cr(cls, predictions, targets) + + def cm(self, predictions, targets): + + return sklearn.metrics.confusion_matrix(targets, predictions) + + def cr(self, predictions, targets): + + return sklearn.metrics.classification_report(targets, predictions) + +@jit(nopython=True) +def mean(data): + + return np.mean(data) + +@jit(nopython=True) +def median(data): + + return np.median(data) + +@jit(nopython=True) +def stdev(data): + + return np.std(data) + +@jit(nopython=True) +def variance(data): + + return np.var(data) + +@jit(nopython=True) +def npmin(data): + + return np.amin(data) + +@jit(nopython=True) +def npmax(data): + + return np.amax(data) + +@jit(forceobj=True) +def kmeans(data, n_clusters=8, init="k-means++", n_init=10, max_iter=300, tol=0.0001, precompute_distances="auto", verbose=0, random_state=None, copy_x=True, n_jobs=None, algorithm="auto"): + + kernel = sklearn.cluster.KMeans(n_clusters = n_clusters, init = init, n_init = n_init, max_iter = max_iter, tol = tol, precompute_distances = precompute_distances, verbose = verbose, random_state = random_state, copy_x = copy_x, n_jobs = n_jobs, algorithm = algorithm) + kernel.fit(data) + predictions = kernel.predict(data) + centers = kernel.cluster_centers_ + + return centers, predictions + +@jit(forceobj=True) +def pca(data, n_components = None, copy = True, whiten = False, svd_solver = "auto", tol = 0.0, iterated_power = "auto", random_state = None): + + kernel = sklearn.decomposition.PCA(n_components = n_components, copy = copy, whiten = whiten, svd_solver = svd_solver, tol = tol, iterated_power = iterated_power, random_state = random_state) + + return kernel.fit_transform(data) + +@jit(forceobj=True) +def decisiontree(data, labels, test_size = 0.3, criterion = "gini", splitter = "default", max_depth = None): #expects *2d data and 1d labels + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.tree.DecisionTreeClassifier(criterion = criterion, splitter = splitter, max_depth = max_depth) + model = model.fit(data_train,labels_train) + predictions = model.predict(data_test) + metrics = ClassificationMetrics(predictions, labels_test) + + return model, metrics + +@jit(forceobj=True) +def knn_classifier(data, labels, test_size = 0.3, algorithm='auto', leaf_size=30, metric='minkowski', metric_params=None, n_jobs=None, n_neighbors=5, p=2, weights='uniform'): #expects *2d data and 1d labels post-scaling + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.neighbors.KNeighborsClassifier() + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + +def knn_regressor(data, outputs, test_size, n_neighbors = 5, weights = "uniform", algorithm = "auto", leaf_size = 30, p = 2, metric = "minkowski", metric_params = None, n_jobs = None): + + data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) + model = sklearn.neighbors.KNeighborsRegressor(n_neighbors = n_neighbors, weights = weights, algorithm = algorithm, leaf_size = leaf_size, p = p, metric = metric, metric_params = metric_params, n_jobs = n_jobs) + model.fit(data_train, outputs_train) + predictions = model.predict(data_test) + + return model, RegressionMetrics(predictions, outputs_test) + +class NaiveBayes: + + def guassian(self, data, labels, test_size = 0.3, priors = None, var_smoothing = 1e-09): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.GaussianNB(priors = priors, var_smoothing = var_smoothing) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + + def multinomial(self, data, labels, test_size = 0.3, alpha=1.0, fit_prior=True, class_prior=None): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.MultinomialNB(alpha = alpha, fit_prior = fit_prior, class_prior = class_prior) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + + def bernoulli(self, data, labels, test_size = 0.3, alpha=1.0, binarize=0.0, fit_prior=True, class_prior=None): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.BernoulliNB(alpha = alpha, binarize = binarize, fit_prior = fit_prior, class_prior = class_prior) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + + def complement(self, data, labels, test_size = 0.3, alpha=1.0, fit_prior=True, class_prior=None, norm=False): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.ComplementNB(alpha = alpha, fit_prior = fit_prior, class_prior = class_prior, norm = norm) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + +class SVM: + + class CustomKernel: + + def __new__(cls, C, kernel, degre, gamma, coef0, shrinking, probability, tol, cache_size, class_weight, verbose, max_iter, decision_function_shape, random_state): + + return sklearn.svm.SVC(C = C, kernel = kernel, gamma = gamma, coef0 = coef0, shrinking = shrinking, probability = probability, tol = tol, cache_size = cache_size, class_weight = class_weight, verbose = verbose, max_iter = max_iter, decision_function_shape = decision_function_shape, random_state = random_state) + + class StandardKernel: + + def __new__(cls, kernel, C=1.0, degree=3, gamma='auto_deprecated', coef0=0.0, shrinking=True, probability=False, tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape='ovr', random_state=None): + + return sklearn.svm.SVC(C = C, kernel = kernel, gamma = gamma, coef0 = coef0, shrinking = shrinking, probability = probability, tol = tol, cache_size = cache_size, class_weight = class_weight, verbose = verbose, max_iter = max_iter, decision_function_shape = decision_function_shape, random_state = random_state) + + class PrebuiltKernel: + + class Linear: + + def __new__(cls): + + return sklearn.svm.SVC(kernel = 'linear') + + class Polynomial: + + def __new__(cls, power, r_bias): + + return sklearn.svm.SVC(kernel = 'polynomial', degree = power, coef0 = r_bias) + + class RBF: + + def __new__(cls, gamma): + + return sklearn.svm.SVC(kernel = 'rbf', gamma = gamma) + + class Sigmoid: + + def __new__(cls, r_bias): + + return sklearn.svm.SVC(kernel = 'sigmoid', coef0 = r_bias) + + def fit(self, kernel, train_data, train_outputs): # expects *2d data, 1d labels or outputs + + return kernel.fit(train_data, train_outputs) + + def eval_classification(self, kernel, test_data, test_outputs): + + predictions = kernel.predict(test_data) + + return ClassificationMetrics(predictions, test_outputs) + + def eval_regression(self, kernel, test_data, test_outputs): + + predictions = kernel.predict(test_data) + + return RegressionMetrics(predictions, test_outputs) + +def random_forest_classifier(data, labels, test_size, n_estimators="warn", criterion="gini", max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features="auto", max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False, class_weight=None): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + kernel = sklearn.ensemble.RandomForestClassifier(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_samples_leaf = min_samples_leaf, min_weight_fraction_leaf = min_weight_fraction_leaf, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start, class_weight = class_weight) + kernel.fit(data_train, labels_train) + predictions = kernel.predict(data_test) + + return kernel, ClassificationMetrics(predictions, labels_test) + +def random_forest_regressor(data, outputs, test_size, n_estimators="warn", criterion="mse", max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features="auto", max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False): + + data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) + kernel = sklearn.ensemble.RandomForestRegressor(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_weight_fraction_leaf = min_weight_fraction_leaf, max_features = max_features, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, min_impurity_split = min_impurity_split, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start) + kernel.fit(data_train, outputs_train) + predictions = kernel.predict(data_test) + + return kernel, RegressionMetrics(predictions, outputs_test) + +class Glicko2: + + _tau = 0.5 + + def getRating(self): + return (self.__rating * 173.7178) + 1500 + + def setRating(self, rating): + self.__rating = (rating - 1500) / 173.7178 + + rating = property(getRating, setRating) + + def getRd(self): + return self.__rd * 173.7178 + + def setRd(self, rd): + self.__rd = rd / 173.7178 + + rd = property(getRd, setRd) + + def __init__(self, rating = 1500, rd = 350, vol = 0.06): + + self.setRating(rating) + self.setRd(rd) + self.vol = vol + + def _preRatingRD(self): + + self.__rd = math.sqrt(math.pow(self.__rd, 2) + math.pow(self.vol, 2)) + + def update_player(self, rating_list, RD_list, outcome_list): + + rating_list = [(x - 1500) / 173.7178 for x in rating_list] + RD_list = [x / 173.7178 for x in RD_list] + + v = self._v(rating_list, RD_list) + self.vol = self._newVol(rating_list, RD_list, outcome_list, v) + self._preRatingRD() + + self.__rd = 1 / math.sqrt((1 / math.pow(self.__rd, 2)) + (1 / v)) + + tempSum = 0 + for i in range(len(rating_list)): + tempSum += self._g(RD_list[i]) * \ + (outcome_list[i] - self._E(rating_list[i], RD_list[i])) + self.__rating += math.pow(self.__rd, 2) * tempSum + + + def _newVol(self, rating_list, RD_list, outcome_list, v): + + i = 0 + delta = self._delta(rating_list, RD_list, outcome_list, v) + a = math.log(math.pow(self.vol, 2)) + tau = self._tau + x0 = a + x1 = 0 + + while x0 != x1: + # New iteration, so x(i) becomes x(i-1) + x0 = x1 + d = math.pow(self.__rating, 2) + v + math.exp(x0) + h1 = -(x0 - a) / math.pow(tau, 2) - 0.5 * math.exp(x0) \ + / d + 0.5 * math.exp(x0) * math.pow(delta / d, 2) + h2 = -1 / math.pow(tau, 2) - 0.5 * math.exp(x0) * \ + (math.pow(self.__rating, 2) + v) \ + / math.pow(d, 2) + 0.5 * math.pow(delta, 2) * math.exp(x0) \ + * (math.pow(self.__rating, 2) + v - math.exp(x0)) / math.pow(d, 3) + x1 = x0 - (h1 / h2) + + return math.exp(x1 / 2) + + def _delta(self, rating_list, RD_list, outcome_list, v): + + tempSum = 0 + for i in range(len(rating_list)): + tempSum += self._g(RD_list[i]) * (outcome_list[i] - self._E(rating_list[i], RD_list[i])) + return v * tempSum + + def _v(self, rating_list, RD_list): + + tempSum = 0 + for i in range(len(rating_list)): + tempE = self._E(rating_list[i], RD_list[i]) + tempSum += math.pow(self._g(RD_list[i]), 2) * tempE * (1 - tempE) + return 1 / tempSum + + def _E(self, p2rating, p2RD): + + return 1 / (1 + math.exp(-1 * self._g(p2RD) * \ + (self.__rating - p2rating))) + + def _g(self, RD): + + return 1 / math.sqrt(1 + 3 * math.pow(RD, 2) / math.pow(math.pi, 2)) + + def did_not_compete(self): + + self._preRatingRD() diff --git a/analysis-master/analysis/regression.py b/analysis-master/analysis/regression.py new file mode 100644 index 00000000..e899e9ff --- /dev/null +++ b/analysis-master/analysis/regression.py @@ -0,0 +1,220 @@ +# Titan Robotics Team 2022: CUDA-based Regressions Module +# Written by Arthur Lu & Jacob Levine +# Notes: +# this module has been automatically inegrated into analysis.py, and should be callable as a class from the package +# this module is cuda-optimized and vectorized (except for one small part) +# setup: + +__version__ = "1.0.0.004" + +# changelog should be viewed using print(analysis.regression.__changelog__) +__changelog__ = """ + 1.0.0.004: + - bug fixes + - fixed changelog + 1.0.0.003: + - bug fixes + 1.0.0.002: + -Added more parameters to log, exponential, polynomial + -Added SigmoidalRegKernelArthur, because Arthur apparently needs + to train the scaling and shifting of sigmoids + 1.0.0.001: + -initial release, with linear, log, exponential, polynomial, and sigmoid kernels + -already vectorized (except for polynomial generation) and CUDA-optimized +""" + +__author__ = ( + "Jacob Levine ", + "Arthur Lu " +) + +__all__ = [ + 'factorial', + 'take_all_pwrs', + 'num_poly_terms', + 'set_device', + 'LinearRegKernel', + 'SigmoidalRegKernel', + 'LogRegKernel', + 'PolyRegKernel', + 'ExpRegKernel', + 'SigmoidalRegKernelArthur', + 'SGDTrain', + 'CustomTrain' +] + +import torch + +global device + +device = "cuda:0" if torch.torch.cuda.is_available() else "cpu" + +#todo: document completely + +def set_device(self, new_device): + device=new_device + +class LinearRegKernel(): + parameters= [] + weights=None + bias=None + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.bias] + def forward(self,mtx): + long_bias=self.bias.repeat([1,mtx.size()[1]]) + return torch.matmul(self.weights,mtx)+long_bias + +class SigmoidalRegKernel(): + parameters= [] + weights=None + bias=None + sigmoid=torch.nn.Sigmoid() + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.bias] + def forward(self,mtx): + long_bias=self.bias.repeat([1,mtx.size()[1]]) + return self.sigmoid(torch.matmul(self.weights,mtx)+long_bias) + +class SigmoidalRegKernelArthur(): + parameters= [] + weights=None + in_bias=None + scal_mult=None + out_bias=None + sigmoid=torch.nn.Sigmoid() + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.in_bias=torch.rand(1, requires_grad=True, device=device) + self.scal_mult=torch.rand(1, requires_grad=True, device=device) + self.out_bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] + def forward(self,mtx): + long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) + long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) + return (self.scal_mult*self.sigmoid(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias + +class LogRegKernel(): + parameters= [] + weights=None + in_bias=None + scal_mult=None + out_bias=None + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.in_bias=torch.rand(1, requires_grad=True, device=device) + self.scal_mult=torch.rand(1, requires_grad=True, device=device) + self.out_bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] + def forward(self,mtx): + long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) + long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) + return (self.scal_mult*torch.log(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias + +class ExpRegKernel(): + parameters= [] + weights=None + in_bias=None + scal_mult=None + out_bias=None + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.in_bias=torch.rand(1, requires_grad=True, device=device) + self.scal_mult=torch.rand(1, requires_grad=True, device=device) + self.out_bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] + def forward(self,mtx): + long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) + long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) + return (self.scal_mult*torch.exp(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias + +class PolyRegKernel(): + parameters= [] + weights=None + bias=None + power=None + def __init__(self, num_vars, power): + self.power=power + num_terms=self.num_poly_terms(num_vars, power) + self.weights=torch.rand(num_terms, requires_grad=True, device=device) + self.bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.bias] + def num_poly_terms(self,num_vars, power): + if power == 0: + return 0 + return int(self.factorial(num_vars+power-1) / self.factorial(power) / self.factorial(num_vars-1)) + self.num_poly_terms(num_vars, power-1) + def factorial(self,n): + if n==0: + return 1 + else: + return n*self.factorial(n-1) + def take_all_pwrs(self, vec, pwr): + #todo: vectorize (kinda) + combins=torch.combinations(vec, r=pwr, with_replacement=True) + out=torch.ones(combins.size()[0]).to(device).to(torch.float) + for i in torch.t(combins).to(device).to(torch.float): + out *= i + if pwr == 1: + return out + else: + return torch.cat((out,self.take_all_pwrs(vec, pwr-1))) + def forward(self,mtx): + #TODO: Vectorize the last part + cols=[] + for i in torch.t(mtx): + cols.append(self.take_all_pwrs(i,self.power)) + new_mtx=torch.t(torch.stack(cols)) + long_bias=self.bias.repeat([1,mtx.size()[1]]) + return torch.matmul(self.weights,new_mtx)+long_bias + +def SGDTrain(self, kernel, data, ground, loss=torch.nn.MSELoss(), iterations=1000, learning_rate=.1, return_losses=False): + optim=torch.optim.SGD(kernel.parameters, lr=learning_rate) + data_cuda=data.to(device) + ground_cuda=ground.to(device) + if (return_losses): + losses=[] + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data_cuda) + ls=loss(pred,ground_cuda) + losses.append(ls.item()) + ls.backward() + optim.step() + return [kernel,losses] + else: + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data_cuda) + ls=loss(pred,ground_cuda) + ls.backward() + optim.step() + return kernel + +def CustomTrain(self, kernel, optim, data, ground, loss=torch.nn.MSELoss(), iterations=1000, return_losses=False): + data_cuda=data.to(device) + ground_cuda=ground.to(device) + if (return_losses): + losses=[] + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data) + ls=loss(pred,ground) + losses.append(ls.item()) + ls.backward() + optim.step() + return [kernel,losses] + else: + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data_cuda) + ls=loss(pred,ground_cuda) + ls.backward() + optim.step() + return kernel \ No newline at end of file diff --git a/analysis-master/analysis/titanlearn.py b/analysis-master/analysis/titanlearn.py new file mode 100644 index 00000000..b69d36e3 --- /dev/null +++ b/analysis-master/analysis/titanlearn.py @@ -0,0 +1,122 @@ +# Titan Robotics Team 2022: ML Module +# Written by Arthur Lu & Jacob Levine +# Notes: +# this should be imported as a python module using 'import titanlearn' +# this should be included in the local directory or environment variable +# this module is optimized for multhreaded computing +# this module learns from its mistakes far faster than 2022's captains +# setup: + +__version__ = "2.0.1.001" + +#changelog should be viewed using print(analysis.__changelog__) +__changelog__ = """changelog: + 2.0.1.001: + - removed matplotlib import + - removed graphloss() + 2.0.1.000: + - added net, dataset, dataloader, and stdtrain template definitions + - added graphloss function + 2.0.0.001: + - added clear functions + 2.0.0.000: + - complete rewrite planned + - depreciated 1.0.0.xxx versions + - added simple training loop + 1.0.0.xxx: + -added generation of ANNS, basic SGD training +""" + +__author__ = ( + "Arthur Lu ," + "Jacob Levine ," + ) + +__all__ = [ + 'clear', + 'net', + 'dataset', + 'dataloader', + 'train', + 'stdtrainer', + ] + +import torch +from os import system, name +import numpy as np + +def clear(): + if name == 'nt': + _ = system('cls') + else: + _ = system('clear') + +class net(torch.nn.Module): #template for standard neural net + def __init__(self): + super(Net, self).__init__() + + def forward(self, input): + pass + +class dataset(torch.utils.data.Dataset): #template for standard dataset + + def __init__(self): + super(torch.utils.data.Dataset).__init__() + + def __getitem__(self, index): + pass + + def __len__(self): + pass + +def dataloader(dataset, batch_size, num_workers, shuffle = True): + + return torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=num_workers) + +def train(device, net, epochs, trainloader, optimizer, criterion): #expects standard dataloader, whch returns (inputs, labels) + + dataset_len = trainloader.dataset.__len__() + iter_count = 0 + running_loss = 0 + running_loss_list = [] + + for epoch in range(epochs): # loop over the dataset multiple times + + for i, data in enumerate(trainloader, 0): + + inputs = data[0].to(device) + labels = data[1].to(device) + + optimizer.zero_grad() + + outputs = net(inputs) + loss = criterion(outputs, labels.to(torch.float)) + + loss.backward() + optimizer.step() + + # monitoring steps below + + iter_count += 1 + running_loss += loss.item() + running_loss_list.append(running_loss) + clear() + + print("training on: " + device) + print("iteration: " + str(i) + "/" + str(int(dataset_len / trainloader.batch_size)) + " | " + "epoch: " + str(epoch) + "/" + str(epochs)) + print("current batch loss: " + str(loss.item)) + print("running loss: " + str(running_loss / iter_count)) + + return net, running_loss_list + print("finished training") + +def stdtrainer(net, criterion, optimizer, dataloader, epochs, batch_size): + + device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + + net = net.to(device) + criterion = criterion.to(device) + optimizer = optimizer.to(device) + trainloader = dataloader + + return train(device, net, epochs, trainloader, optimizer, criterion) \ No newline at end of file diff --git a/analysis-master/analysis/trueskill.py b/analysis-master/analysis/trueskill.py new file mode 100644 index 00000000..116357df --- /dev/null +++ b/analysis-master/analysis/trueskill.py @@ -0,0 +1,907 @@ +from __future__ import absolute_import + +from itertools import chain +import math + +from six import iteritems +from six.moves import map, range, zip +from six import iterkeys + +import copy +try: + from numbers import Number +except ImportError: + Number = (int, long, float, complex) + +inf = float('inf') + +class Gaussian(object): + #: Precision, the inverse of the variance. + pi = 0 + #: Precision adjusted mean, the precision multiplied by the mean. + tau = 0 + + def __init__(self, mu=None, sigma=None, pi=0, tau=0): + if mu is not None: + if sigma is None: + raise TypeError('sigma argument is needed') + elif sigma == 0: + raise ValueError('sigma**2 should be greater than 0') + pi = sigma ** -2 + tau = pi * mu + self.pi = pi + self.tau = tau + + @property + def mu(self): + return self.pi and self.tau / self.pi + + @property + def sigma(self): + return math.sqrt(1 / self.pi) if self.pi else inf + + def __mul__(self, other): + pi, tau = self.pi + other.pi, self.tau + other.tau + return Gaussian(pi=pi, tau=tau) + + def __truediv__(self, other): + pi, tau = self.pi - other.pi, self.tau - other.tau + return Gaussian(pi=pi, tau=tau) + + __div__ = __truediv__ # for Python 2 + + def __eq__(self, other): + return self.pi == other.pi and self.tau == other.tau + + def __lt__(self, other): + return self.mu < other.mu + + def __le__(self, other): + return self.mu <= other.mu + + def __gt__(self, other): + return self.mu > other.mu + + def __ge__(self, other): + return self.mu >= other.mu + + def __repr__(self): + return 'N(mu={:.3f}, sigma={:.3f})'.format(self.mu, self.sigma) + + def _repr_latex_(self): + latex = r'\mathcal{{ N }}( {:.3f}, {:.3f}^2 )'.format(self.mu, self.sigma) + return '$%s$' % latex + +class Matrix(list): + def __init__(self, src, height=None, width=None): + if callable(src): + f, src = src, {} + size = [height, width] + if not height: + def set_height(height): + size[0] = height + size[0] = set_height + if not width: + def set_width(width): + size[1] = width + size[1] = set_width + try: + for (r, c), val in f(*size): + src[r, c] = val + except TypeError: + raise TypeError('A callable src must return an interable ' + 'which generates a tuple containing ' + 'coordinate and value') + height, width = tuple(size) + if height is None or width is None: + raise TypeError('A callable src must call set_height and ' + 'set_width if the size is non-deterministic') + if isinstance(src, list): + is_number = lambda x: isinstance(x, Number) + unique_col_sizes = set(map(len, src)) + everything_are_number = filter(is_number, sum(src, [])) + if len(unique_col_sizes) != 1 or not everything_are_number: + raise ValueError('src must be a rectangular array of numbers') + two_dimensional_array = src + elif isinstance(src, dict): + if not height or not width: + w = h = 0 + for r, c in iterkeys(src): + if not height: + h = max(h, r + 1) + if not width: + w = max(w, c + 1) + if not height: + height = h + if not width: + width = w + two_dimensional_array = [] + for r in range(height): + row = [] + two_dimensional_array.append(row) + for c in range(width): + row.append(src.get((r, c), 0)) + else: + raise TypeError('src must be a list or dict or callable') + super(Matrix, self).__init__(two_dimensional_array) + + @property + def height(self): + return len(self) + + @property + def width(self): + return len(self[0]) + + def transpose(self): + height, width = self.height, self.width + src = {} + for c in range(width): + for r in range(height): + src[c, r] = self[r][c] + return type(self)(src, height=width, width=height) + + def minor(self, row_n, col_n): + height, width = self.height, self.width + if not (0 <= row_n < height): + raise ValueError('row_n should be between 0 and %d' % height) + elif not (0 <= col_n < width): + raise ValueError('col_n should be between 0 and %d' % width) + two_dimensional_array = [] + for r in range(height): + if r == row_n: + continue + row = [] + two_dimensional_array.append(row) + for c in range(width): + if c == col_n: + continue + row.append(self[r][c]) + return type(self)(two_dimensional_array) + + def determinant(self): + height, width = self.height, self.width + if height != width: + raise ValueError('Only square matrix can calculate a determinant') + tmp, rv = copy.deepcopy(self), 1. + for c in range(width - 1, 0, -1): + pivot, r = max((abs(tmp[r][c]), r) for r in range(c + 1)) + pivot = tmp[r][c] + if not pivot: + return 0. + tmp[r], tmp[c] = tmp[c], tmp[r] + if r != c: + rv = -rv + rv *= pivot + fact = -1. / pivot + for r in range(c): + f = fact * tmp[r][c] + for x in range(c): + tmp[r][x] += f * tmp[c][x] + return rv * tmp[0][0] + + def adjugate(self): + height, width = self.height, self.width + if height != width: + raise ValueError('Only square matrix can be adjugated') + if height == 2: + a, b = self[0][0], self[0][1] + c, d = self[1][0], self[1][1] + return type(self)([[d, -b], [-c, a]]) + src = {} + for r in range(height): + for c in range(width): + sign = -1 if (r + c) % 2 else 1 + src[r, c] = self.minor(r, c).determinant() * sign + return type(self)(src, height, width) + + def inverse(self): + if self.height == self.width == 1: + return type(self)([[1. / self[0][0]]]) + return (1. / self.determinant()) * self.adjugate() + + def __add__(self, other): + height, width = self.height, self.width + if (height, width) != (other.height, other.width): + raise ValueError('Must be same size') + src = {} + for r in range(height): + for c in range(width): + src[r, c] = self[r][c] + other[r][c] + return type(self)(src, height, width) + + def __mul__(self, other): + if self.width != other.height: + raise ValueError('Bad size') + height, width = self.height, other.width + src = {} + for r in range(height): + for c in range(width): + src[r, c] = sum(self[r][x] * other[x][c] + for x in range(self.width)) + return type(self)(src, height, width) + + def __rmul__(self, other): + if not isinstance(other, Number): + raise TypeError('The operand should be a number') + height, width = self.height, self.width + src = {} + for r in range(height): + for c in range(width): + src[r, c] = other * self[r][c] + return type(self)(src, height, width) + + def __repr__(self): + return '{}({})'.format(type(self).__name__, super(Matrix, self).__repr__()) + + def _repr_latex_(self): + rows = [' && '.join(['%.3f' % cell for cell in row]) for row in self] + latex = r'\begin{matrix} %s \end{matrix}' % r'\\'.join(rows) + return '$%s$' % latex + +def _gen_erfcinv(erfc, math=math): + def erfcinv(y): + """The inverse function of erfc.""" + if y >= 2: + return -100. + elif y <= 0: + return 100. + zero_point = y < 1 + if not zero_point: + y = 2 - y + t = math.sqrt(-2 * math.log(y / 2.)) + x = -0.70711 * \ + ((2.30753 + t * 0.27061) / (1. + t * (0.99229 + t * 0.04481)) - t) + for i in range(2): + err = erfc(x) - y + x += err / (1.12837916709551257 * math.exp(-(x ** 2)) - x * err) + return x if zero_point else -x + return erfcinv + +def _gen_ppf(erfc, math=math): + erfcinv = _gen_erfcinv(erfc, math) + def ppf(x, mu=0, sigma=1): + return mu - sigma * math.sqrt(2) * erfcinv(2 * x) + return ppf + +def erfc(x): + z = abs(x) + t = 1. / (1. + z / 2.) + r = t * math.exp(-z * z - 1.26551223 + t * (1.00002368 + t * ( + 0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * ( + 0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * ( + -0.82215223 + t * 0.17087277 + ))) + ))) + ))) + return 2. - r if x < 0 else r + +def cdf(x, mu=0, sigma=1): + return 0.5 * erfc(-(x - mu) / (sigma * math.sqrt(2))) + + +def pdf(x, mu=0, sigma=1): + return (1 / math.sqrt(2 * math.pi) * abs(sigma) * + math.exp(-(((x - mu) / abs(sigma)) ** 2 / 2))) + +ppf = _gen_ppf(erfc) + +def choose_backend(backend): + if backend is None: # fallback + return cdf, pdf, ppf + elif backend == 'mpmath': + try: + import mpmath + except ImportError: + raise ImportError('Install "mpmath" to use this backend') + return mpmath.ncdf, mpmath.npdf, _gen_ppf(mpmath.erfc, math=mpmath) + elif backend == 'scipy': + try: + from scipy.stats import norm + except ImportError: + raise ImportError('Install "scipy" to use this backend') + return norm.cdf, norm.pdf, norm.ppf + raise ValueError('%r backend is not defined' % backend) + +def available_backends(): + backends = [None] + for backend in ['mpmath', 'scipy']: + try: + __import__(backend) + except ImportError: + continue + backends.append(backend) + return backends + +class Node(object): + + pass + +class Variable(Node, Gaussian): + + def __init__(self): + self.messages = {} + super(Variable, self).__init__() + + def set(self, val): + delta = self.delta(val) + self.pi, self.tau = val.pi, val.tau + return delta + + def delta(self, other): + pi_delta = abs(self.pi - other.pi) + if pi_delta == inf: + return 0. + return max(abs(self.tau - other.tau), math.sqrt(pi_delta)) + + def update_message(self, factor, pi=0, tau=0, message=None): + message = message or Gaussian(pi=pi, tau=tau) + old_message, self[factor] = self[factor], message + return self.set(self / old_message * message) + + def update_value(self, factor, pi=0, tau=0, value=None): + value = value or Gaussian(pi=pi, tau=tau) + old_message = self[factor] + self[factor] = value * old_message / self + return self.set(value) + + def __getitem__(self, factor): + return self.messages[factor] + + def __setitem__(self, factor, message): + self.messages[factor] = message + + def __repr__(self): + args = (type(self).__name__, super(Variable, self).__repr__(), + len(self.messages), '' if len(self.messages) == 1 else 's') + return '<%s %s with %d connection%s>' % args + + +class Factor(Node): + + def __init__(self, variables): + self.vars = variables + for var in variables: + var[self] = Gaussian() + + def down(self): + return 0 + + def up(self): + return 0 + + @property + def var(self): + assert len(self.vars) == 1 + return self.vars[0] + + def __repr__(self): + args = (type(self).__name__, len(self.vars), + '' if len(self.vars) == 1 else 's') + return '<%s with %d connection%s>' % args + + +class PriorFactor(Factor): + + def __init__(self, var, val, dynamic=0): + super(PriorFactor, self).__init__([var]) + self.val = val + self.dynamic = dynamic + + def down(self): + sigma = math.sqrt(self.val.sigma ** 2 + self.dynamic ** 2) + value = Gaussian(self.val.mu, sigma) + return self.var.update_value(self, value=value) + + +class LikelihoodFactor(Factor): + + def __init__(self, mean_var, value_var, variance): + super(LikelihoodFactor, self).__init__([mean_var, value_var]) + self.mean = mean_var + self.value = value_var + self.variance = variance + + def calc_a(self, var): + return 1. / (1. + self.variance * var.pi) + + def down(self): + # update value. + msg = self.mean / self.mean[self] + a = self.calc_a(msg) + return self.value.update_message(self, a * msg.pi, a * msg.tau) + + def up(self): + # update mean. + msg = self.value / self.value[self] + a = self.calc_a(msg) + return self.mean.update_message(self, a * msg.pi, a * msg.tau) + + +class SumFactor(Factor): + + def __init__(self, sum_var, term_vars, coeffs): + super(SumFactor, self).__init__([sum_var] + term_vars) + self.sum = sum_var + self.terms = term_vars + self.coeffs = coeffs + + def down(self): + vals = self.terms + msgs = [var[self] for var in vals] + return self.update(self.sum, vals, msgs, self.coeffs) + + def up(self, index=0): + coeff = self.coeffs[index] + coeffs = [] + for x, c in enumerate(self.coeffs): + try: + if x == index: + coeffs.append(1. / coeff) + else: + coeffs.append(-c / coeff) + except ZeroDivisionError: + coeffs.append(0.) + vals = self.terms[:] + vals[index] = self.sum + msgs = [var[self] for var in vals] + return self.update(self.terms[index], vals, msgs, coeffs) + + def update(self, var, vals, msgs, coeffs): + pi_inv = 0 + mu = 0 + for val, msg, coeff in zip(vals, msgs, coeffs): + div = val / msg + mu += coeff * div.mu + if pi_inv == inf: + continue + try: + # numpy.float64 handles floating-point error by different way. + # For example, it can just warn RuntimeWarning on n/0 problem + # instead of throwing ZeroDivisionError. So div.pi, the + # denominator has to be a built-in float. + pi_inv += coeff ** 2 / float(div.pi) + except ZeroDivisionError: + pi_inv = inf + pi = 1. / pi_inv + tau = pi * mu + return var.update_message(self, pi, tau) + + +class TruncateFactor(Factor): + + def __init__(self, var, v_func, w_func, draw_margin): + super(TruncateFactor, self).__init__([var]) + self.v_func = v_func + self.w_func = w_func + self.draw_margin = draw_margin + + def up(self): + val = self.var + msg = self.var[self] + div = val / msg + sqrt_pi = math.sqrt(div.pi) + args = (div.tau / sqrt_pi, self.draw_margin * sqrt_pi) + v = self.v_func(*args) + w = self.w_func(*args) + denom = (1. - w) + pi, tau = div.pi / denom, (div.tau + sqrt_pi * v) / denom + return val.update_value(self, pi, tau) + +#: Default initial mean of ratings. +MU = 25. +#: Default initial standard deviation of ratings. +SIGMA = MU / 3 +#: Default distance that guarantees about 76% chance of winning. +BETA = SIGMA / 2 +#: Default dynamic factor. +TAU = SIGMA / 100 +#: Default draw probability of the game. +DRAW_PROBABILITY = .10 +#: A basis to check reliability of the result. +DELTA = 0.0001 + + +def calc_draw_probability(draw_margin, size, env=None): + if env is None: + env = global_env() + return 2 * env.cdf(draw_margin / (math.sqrt(size) * env.beta)) - 1 + + +def calc_draw_margin(draw_probability, size, env=None): + if env is None: + env = global_env() + return env.ppf((draw_probability + 1) / 2.) * math.sqrt(size) * env.beta + + +def _team_sizes(rating_groups): + team_sizes = [0] + for group in rating_groups: + team_sizes.append(len(group) + team_sizes[-1]) + del team_sizes[0] + return team_sizes + + +def _floating_point_error(env): + if env.backend == 'mpmath': + msg = 'Set "mpmath.mp.dps" to higher' + else: + msg = 'Cannot calculate correctly, set backend to "mpmath"' + return FloatingPointError(msg) + + +class Rating(Gaussian): + def __init__(self, mu=None, sigma=None): + if isinstance(mu, tuple): + mu, sigma = mu + elif isinstance(mu, Gaussian): + mu, sigma = mu.mu, mu.sigma + if mu is None: + mu = global_env().mu + if sigma is None: + sigma = global_env().sigma + super(Rating, self).__init__(mu, sigma) + + def __int__(self): + return int(self.mu) + + def __long__(self): + return long(self.mu) + + def __float__(self): + return float(self.mu) + + def __iter__(self): + return iter((self.mu, self.sigma)) + + def __repr__(self): + c = type(self) + args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma) + return '%s(mu=%.3f, sigma=%.3f)' % args + + +class TrueSkill(object): + def __init__(self, mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, + draw_probability=DRAW_PROBABILITY, backend=None): + self.mu = mu + self.sigma = sigma + self.beta = beta + self.tau = tau + self.draw_probability = draw_probability + self.backend = backend + if isinstance(backend, tuple): + self.cdf, self.pdf, self.ppf = backend + else: + self.cdf, self.pdf, self.ppf = choose_backend(backend) + + def create_rating(self, mu=None, sigma=None): + if mu is None: + mu = self.mu + if sigma is None: + sigma = self.sigma + return Rating(mu, sigma) + + def v_win(self, diff, draw_margin): + x = diff - draw_margin + denom = self.cdf(x) + return (self.pdf(x) / denom) if denom else -x + + def v_draw(self, diff, draw_margin): + abs_diff = abs(diff) + a, b = draw_margin - abs_diff, -draw_margin - abs_diff + denom = self.cdf(a) - self.cdf(b) + numer = self.pdf(b) - self.pdf(a) + return ((numer / denom) if denom else a) * (-1 if diff < 0 else +1) + + def w_win(self, diff, draw_margin): + x = diff - draw_margin + v = self.v_win(diff, draw_margin) + w = v * (v + x) + if 0 < w < 1: + return w + raise _floating_point_error(self) + + def w_draw(self, diff, draw_margin): + abs_diff = abs(diff) + a, b = draw_margin - abs_diff, -draw_margin - abs_diff + denom = self.cdf(a) - self.cdf(b) + if not denom: + raise _floating_point_error(self) + v = self.v_draw(abs_diff, draw_margin) + return (v ** 2) + (a * self.pdf(a) - b * self.pdf(b)) / denom + + def validate_rating_groups(self, rating_groups): + # check group sizes + if len(rating_groups) < 2: + raise ValueError('Need multiple rating groups') + elif not all(rating_groups): + raise ValueError('Each group must contain multiple ratings') + # check group types + group_types = set(map(type, rating_groups)) + if len(group_types) != 1: + raise TypeError('All groups should be same type') + elif group_types.pop() is Rating: + raise TypeError('Rating cannot be a rating group') + # normalize rating_groups + if isinstance(rating_groups[0], dict): + dict_rating_groups = rating_groups + rating_groups = [] + keys = [] + for dict_rating_group in dict_rating_groups: + rating_group, key_group = [], [] + for key, rating in iteritems(dict_rating_group): + rating_group.append(rating) + key_group.append(key) + rating_groups.append(tuple(rating_group)) + keys.append(tuple(key_group)) + else: + rating_groups = list(rating_groups) + keys = None + return rating_groups, keys + + def validate_weights(self, weights, rating_groups, keys=None): + if weights is None: + weights = [(1,) * len(g) for g in rating_groups] + elif isinstance(weights, dict): + weights_dict, weights = weights, [] + for x, group in enumerate(rating_groups): + w = [] + weights.append(w) + for y, rating in enumerate(group): + if keys is not None: + y = keys[x][y] + w.append(weights_dict.get((x, y), 1)) + return weights + + def factor_graph_builders(self, rating_groups, ranks, weights): + flatten_ratings = sum(map(tuple, rating_groups), ()) + flatten_weights = sum(map(tuple, weights), ()) + size = len(flatten_ratings) + group_size = len(rating_groups) + # create variables + rating_vars = [Variable() for x in range(size)] + perf_vars = [Variable() for x in range(size)] + team_perf_vars = [Variable() for x in range(group_size)] + team_diff_vars = [Variable() for x in range(group_size - 1)] + team_sizes = _team_sizes(rating_groups) + # layer builders + def build_rating_layer(): + for rating_var, rating in zip(rating_vars, flatten_ratings): + yield PriorFactor(rating_var, rating, self.tau) + def build_perf_layer(): + for rating_var, perf_var in zip(rating_vars, perf_vars): + yield LikelihoodFactor(rating_var, perf_var, self.beta ** 2) + def build_team_perf_layer(): + for team, team_perf_var in enumerate(team_perf_vars): + if team > 0: + start = team_sizes[team - 1] + else: + start = 0 + end = team_sizes[team] + child_perf_vars = perf_vars[start:end] + coeffs = flatten_weights[start:end] + yield SumFactor(team_perf_var, child_perf_vars, coeffs) + def build_team_diff_layer(): + for team, team_diff_var in enumerate(team_diff_vars): + yield SumFactor(team_diff_var, + team_perf_vars[team:team + 2], [+1, -1]) + def build_trunc_layer(): + for x, team_diff_var in enumerate(team_diff_vars): + if callable(self.draw_probability): + # dynamic draw probability + team_perf1, team_perf2 = team_perf_vars[x:x + 2] + args = (Rating(team_perf1), Rating(team_perf2), self) + draw_probability = self.draw_probability(*args) + else: + # static draw probability + draw_probability = self.draw_probability + size = sum(map(len, rating_groups[x:x + 2])) + draw_margin = calc_draw_margin(draw_probability, size, self) + if ranks[x] == ranks[x + 1]: # is a tie? + v_func, w_func = self.v_draw, self.w_draw + else: + v_func, w_func = self.v_win, self.w_win + yield TruncateFactor(team_diff_var, + v_func, w_func, draw_margin) + # build layers + return (build_rating_layer, build_perf_layer, build_team_perf_layer, + build_team_diff_layer, build_trunc_layer) + + def run_schedule(self, build_rating_layer, build_perf_layer, + build_team_perf_layer, build_team_diff_layer, + build_trunc_layer, min_delta=DELTA): + if min_delta <= 0: + raise ValueError('min_delta must be greater than 0') + layers = [] + def build(builders): + layers_built = [list(build()) for build in builders] + layers.extend(layers_built) + return layers_built + # gray arrows + layers_built = build([build_rating_layer, + build_perf_layer, + build_team_perf_layer]) + rating_layer, perf_layer, team_perf_layer = layers_built + for f in chain(*layers_built): + f.down() + # arrow #1, #2, #3 + team_diff_layer, trunc_layer = build([build_team_diff_layer, + build_trunc_layer]) + team_diff_len = len(team_diff_layer) + for x in range(10): + if team_diff_len == 1: + # only two teams + team_diff_layer[0].down() + delta = trunc_layer[0].up() + else: + # multiple teams + delta = 0 + for x in range(team_diff_len - 1): + team_diff_layer[x].down() + delta = max(delta, trunc_layer[x].up()) + team_diff_layer[x].up(1) # up to right variable + for x in range(team_diff_len - 1, 0, -1): + team_diff_layer[x].down() + delta = max(delta, trunc_layer[x].up()) + team_diff_layer[x].up(0) # up to left variable + # repeat until to small update + if delta <= min_delta: + break + # up both ends + team_diff_layer[0].up(0) + team_diff_layer[team_diff_len - 1].up(1) + # up the remainder of the black arrows + for f in team_perf_layer: + for x in range(len(f.vars) - 1): + f.up(x) + for f in perf_layer: + f.up() + return layers + + def rate(self, rating_groups, ranks=None, weights=None, min_delta=DELTA): + rating_groups, keys = self.validate_rating_groups(rating_groups) + weights = self.validate_weights(weights, rating_groups, keys) + group_size = len(rating_groups) + if ranks is None: + ranks = range(group_size) + elif len(ranks) != group_size: + raise ValueError('Wrong ranks') + # sort rating groups by rank + by_rank = lambda x: x[1][1] + sorting = sorted(enumerate(zip(rating_groups, ranks, weights)), + key=by_rank) + sorted_rating_groups, sorted_ranks, sorted_weights = [], [], [] + for x, (g, r, w) in sorting: + sorted_rating_groups.append(g) + sorted_ranks.append(r) + # make weights to be greater than 0 + sorted_weights.append(max(min_delta, w_) for w_ in w) + # build factor graph + args = (sorted_rating_groups, sorted_ranks, sorted_weights) + builders = self.factor_graph_builders(*args) + args = builders + (min_delta,) + layers = self.run_schedule(*args) + # make result + rating_layer, team_sizes = layers[0], _team_sizes(sorted_rating_groups) + transformed_groups = [] + for start, end in zip([0] + team_sizes[:-1], team_sizes): + group = [] + for f in rating_layer[start:end]: + group.append(Rating(float(f.var.mu), float(f.var.sigma))) + transformed_groups.append(tuple(group)) + by_hint = lambda x: x[0] + unsorting = sorted(zip((x for x, __ in sorting), transformed_groups), + key=by_hint) + if keys is None: + return [g for x, g in unsorting] + # restore the structure with input dictionary keys + return [dict(zip(keys[x], g)) for x, g in unsorting] + + def quality(self, rating_groups, weights=None): + rating_groups, keys = self.validate_rating_groups(rating_groups) + weights = self.validate_weights(weights, rating_groups, keys) + flatten_ratings = sum(map(tuple, rating_groups), ()) + flatten_weights = sum(map(tuple, weights), ()) + length = len(flatten_ratings) + # a vector of all of the skill means + mean_matrix = Matrix([[r.mu] for r in flatten_ratings]) + # a matrix whose diagonal values are the variances (sigma ** 2) of each + # of the players. + def variance_matrix(height, width): + variances = (r.sigma ** 2 for r in flatten_ratings) + for x, variance in enumerate(variances): + yield (x, x), variance + variance_matrix = Matrix(variance_matrix, length, length) + # the player-team assignment and comparison matrix + def rotated_a_matrix(set_height, set_width): + t = 0 + for r, (cur, _next) in enumerate(zip(rating_groups[:-1], + rating_groups[1:])): + for x in range(t, t + len(cur)): + yield (r, x), flatten_weights[x] + t += 1 + x += 1 + for x in range(x, x + len(_next)): + yield (r, x), -flatten_weights[x] + set_height(r + 1) + set_width(x + 1) + rotated_a_matrix = Matrix(rotated_a_matrix) + a_matrix = rotated_a_matrix.transpose() + # match quality further derivation + _ata = (self.beta ** 2) * rotated_a_matrix * a_matrix + _atsa = rotated_a_matrix * variance_matrix * a_matrix + start = mean_matrix.transpose() * a_matrix + middle = _ata + _atsa + end = rotated_a_matrix * mean_matrix + # make result + e_arg = (-0.5 * start * middle.inverse() * end).determinant() + s_arg = _ata.determinant() / middle.determinant() + return math.exp(e_arg) * math.sqrt(s_arg) + + def expose(self, rating): + k = self.mu / self.sigma + return rating.mu - k * rating.sigma + + def make_as_global(self): + return setup(env=self) + + def __repr__(self): + c = type(self) + if callable(self.draw_probability): + f = self.draw_probability + draw_probability = '.'.join([f.__module__, f.__name__]) + else: + draw_probability = '%.1f%%' % (self.draw_probability * 100) + if self.backend is None: + backend = '' + elif isinstance(self.backend, tuple): + backend = ', backend=...' + else: + backend = ', backend=%r' % self.backend + args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma, + self.beta, self.tau, draw_probability, backend) + return ('%s(mu=%.3f, sigma=%.3f, beta=%.3f, tau=%.3f, ' + 'draw_probability=%s%s)' % args) + + +def rate_1vs1(rating1, rating2, drawn=False, min_delta=DELTA, env=None): + if env is None: + env = global_env() + ranks = [0, 0 if drawn else 1] + teams = env.rate([(rating1,), (rating2,)], ranks, min_delta=min_delta) + return teams[0][0], teams[1][0] + + +def quality_1vs1(rating1, rating2, env=None): + if env is None: + env = global_env() + return env.quality([(rating1,), (rating2,)]) + + +def global_env(): + try: + global_env.__trueskill__ + except AttributeError: + # setup the default environment + setup() + return global_env.__trueskill__ + + +def setup(mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, + draw_probability=DRAW_PROBABILITY, backend=None, env=None): + if env is None: + env = TrueSkill(mu, sigma, beta, tau, draw_probability, backend) + global_env.__trueskill__ = env + return env + + +def rate(rating_groups, ranks=None, weights=None, min_delta=DELTA): + return global_env().rate(rating_groups, ranks, weights, min_delta) + + +def quality(rating_groups, weights=None): + return global_env().quality(rating_groups, weights) + + +def expose(rating): + return global_env().expose(rating) \ No newline at end of file diff --git a/analysis-master/analysis/visualization.py b/analysis-master/analysis/visualization.py new file mode 100644 index 00000000..72358662 --- /dev/null +++ b/analysis-master/analysis/visualization.py @@ -0,0 +1,34 @@ +# Titan Robotics Team 2022: Visualization Module +# Written by Arthur Lu & Jacob Levine +# Notes: +# this should be imported as a python module using 'import visualization' +# this should be included in the local directory or environment variable +# fancy +# setup: + +__version__ = "1.0.0.000" + +#changelog should be viewed using print(analysis.__changelog__) +__changelog__ = """changelog: + 1.0.0.000: + - created visualization.py + - added graphloss() + - added imports +""" + +__author__ = ( + "Arthur Lu ," + "Jacob Levine ," + ) + +__all__ = [ + 'graphloss', + ] + +import matplotlib.pyplot as plt + +def graphloss(losses): + + x = range(0, len(losses)) + plt.plot(x, losses) + plt.show() \ No newline at end of file diff --git a/analysis-master/build.sh b/analysis-master/build.sh new file mode 100644 index 00000000..c6ac05d8 --- /dev/null +++ b/analysis-master/build.sh @@ -0,0 +1 @@ +python3 setup.py sdist bdist_wheel \ No newline at end of file diff --git a/analysis-master/build/lib/analysis/__init__.py b/analysis-master/build/lib/analysis/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/analysis-master/build/lib/analysis/analysis.py b/analysis-master/build/lib/analysis/analysis.py new file mode 100644 index 00000000..c0f0de7f --- /dev/null +++ b/analysis-master/build/lib/analysis/analysis.py @@ -0,0 +1,790 @@ +# Titan Robotics Team 2022: Data Analysis Module +# Written by Arthur Lu & Jacob Levine +# Notes: +# this should be imported as a python module using 'import analysis' +# this should be included in the local directory or environment variable +# this module has been optimized for multhreaded computing +# current benchmark of optimization: 1.33 times faster +# setup: + +__version__ = "1.1.13.006" + +# changelog should be viewed using print(analysis.__changelog__) +__changelog__ = """changelog: + 1.1.13.006: + - cleaned up imports + 1.1.13.005: + - cleaned up package + 1.1.13.004: + - small fixes to regression to improve performance + 1.1.13.003: + - filtered nans from regression + 1.1.13.002: + - removed torch requirement, and moved Regression back to regression.py + 1.1.13.001: + - bug fix with linear regression not returning a proper value + - cleaned up regression + - fixed bug with polynomial regressions + 1.1.13.000: + - fixed all regressions to now properly work + 1.1.12.006: + - fixed bg with a division by zero in histo_analysis + 1.1.12.005: + - fixed numba issues by removing numba from elo, glicko2 and trueskill + 1.1.12.004: + - renamed gliko to glicko + 1.1.12.003: + - removed depreciated code + 1.1.12.002: + - removed team first time trueskill instantiation in favor of integration in superscript.py + 1.1.12.001: + - improved readibility of regression outputs by stripping tensor data + - used map with lambda to acheive the improved readibility + - lost numba jit support with regression, and generated_jit hangs at execution + - TODO: reimplement correct numba integration in regression + 1.1.12.000: + - temporarily fixed polynomial regressions by using sklearn's PolynomialFeatures + 1.1.11.010: + - alphabeticaly ordered import lists + 1.1.11.009: + - bug fixes + 1.1.11.008: + - bug fixes + 1.1.11.007: + - bug fixes + 1.1.11.006: + - tested min and max + - bug fixes + 1.1.11.005: + - added min and max in basic_stats + 1.1.11.004: + - bug fixes + 1.1.11.003: + - bug fixes + 1.1.11.002: + - consolidated metrics + - fixed __all__ + 1.1.11.001: + - added test/train split to RandomForestClassifier and RandomForestRegressor + 1.1.11.000: + - added RandomForestClassifier and RandomForestRegressor + - note: untested + 1.1.10.000: + - added numba.jit to remaining functions + 1.1.9.002: + - kernelized PCA and KNN + 1.1.9.001: + - fixed bugs with SVM and NaiveBayes + 1.1.9.000: + - added SVM class, subclasses, and functions + - note: untested + 1.1.8.000: + - added NaiveBayes classification engine + - note: untested + 1.1.7.000: + - added knn() + - added confusion matrix to decisiontree() + 1.1.6.002: + - changed layout of __changelog to be vscode friendly + 1.1.6.001: + - added additional hyperparameters to decisiontree() + 1.1.6.000: + - fixed __version__ + - fixed __all__ order + - added decisiontree() + 1.1.5.003: + - added pca + 1.1.5.002: + - reduced import list + - added kmeans clustering engine + 1.1.5.001: + - simplified regression by using .to(device) + 1.1.5.000: + - added polynomial regression to regression(); untested + 1.1.4.000: + - added trueskill() + 1.1.3.002: + - renamed regression class to Regression, regression_engine() to regression gliko2_engine class to Gliko2 + 1.1.3.001: + - changed glicko2() to return tuple instead of array + 1.1.3.000: + - added glicko2_engine class and glicko() + - verified glicko2() accuracy + 1.1.2.003: + - fixed elo() + 1.1.2.002: + - added elo() + - elo() has bugs to be fixed + 1.1.2.001: + - readded regrression import + 1.1.2.000: + - integrated regression.py as regression class + - removed regression import + - fixed metadata for regression class + - fixed metadata for analysis class + 1.1.1.001: + - regression_engine() bug fixes, now actaully regresses + 1.1.1.000: + - added regression_engine() + - added all regressions except polynomial + 1.1.0.007: + - updated _init_device() + 1.1.0.006: + - removed useless try statements + 1.1.0.005: + - removed impossible outcomes + 1.1.0.004: + - added performance metrics (r^2, mse, rms) + 1.1.0.003: + - resolved nopython mode for mean, median, stdev, variance + 1.1.0.002: + - snapped (removed) majority of uneeded imports + - forced object mode (bad) on all jit + - TODO: stop numba complaining about not being able to compile in nopython mode + 1.1.0.001: + - removed from sklearn import * to resolve uneeded wildcard imports + 1.1.0.000: + - removed c_entities,nc_entities,obstacles,objectives from __all__ + - applied numba.jit to all functions + - depreciated and removed stdev_z_split + - cleaned up histo_analysis to include numpy and numba.jit optimizations + - depreciated and removed all regression functions in favor of future pytorch optimizer + - depreciated and removed all nonessential functions (basic_analysis, benchmark, strip_data) + - optimized z_normalize using sklearn.preprocessing.normalize + - TODO: implement kernel/function based pytorch regression optimizer + 1.0.9.000: + - refactored + - numpyed everything + - removed stats in favor of numpy functions + 1.0.8.005: + - minor fixes + 1.0.8.004: + - removed a few unused dependencies + 1.0.8.003: + - added p_value function + 1.0.8.002: + - updated __all__ correctly to contain changes made in v 1.0.8.000 and v 1.0.8.001 + 1.0.8.001: + - refactors + - bugfixes + 1.0.8.000: + - depreciated histo_analysis_old + - depreciated debug + - altered basic_analysis to take array data instead of filepath + - refactor + - optimization + 1.0.7.002: + - bug fixes + 1.0.7.001: + - bug fixes + 1.0.7.000: + - added tanh_regression (logistical regression) + - bug fixes + 1.0.6.005: + - added z_normalize function to normalize dataset + - bug fixes + 1.0.6.004: + - bug fixes + 1.0.6.003: + - bug fixes + 1.0.6.002: + - bug fixes + 1.0.6.001: + - corrected __all__ to contain all of the functions + 1.0.6.000: + - added calc_overfit, which calculates two measures of overfit, error and performance + - added calculating overfit to optimize_regression + 1.0.5.000: + - added optimize_regression function, which is a sample function to find the optimal regressions + - optimize_regression function filters out some overfit funtions (functions with r^2 = 1) + - planned addition: overfit detection in the optimize_regression function + 1.0.4.002: + - added __changelog__ + - updated debug function with log and exponential regressions + 1.0.4.001: + - added log regressions + - added exponential regressions + - added log_regression and exp_regression to __all__ + 1.0.3.008: + - added debug function to further consolidate functions + 1.0.3.007: + - added builtin benchmark function + - added builtin random (linear) data generation function + - added device initialization (_init_device) + 1.0.3.006: + - reorganized the imports list to be in alphabetical order + - added search and regurgitate functions to c_entities, nc_entities, obstacles, objectives + 1.0.3.005: + - major bug fixes + - updated historical analysis + - depreciated old historical analysis + 1.0.3.004: + - added __version__, __author__, __all__ + - added polynomial regression + - added root mean squared function + - added r squared function + 1.0.3.003: + - bug fixes + - added c_entities + 1.0.3.002: + - bug fixes + - added nc_entities, obstacles, objectives + - consolidated statistics.py to analysis.py + 1.0.3.001: + - compiled 1d, column, and row basic stats into basic stats function + 1.0.3.000: + - added historical analysis function + 1.0.2.xxx: + - added z score test + 1.0.1.xxx: + - major bug fixes + 1.0.0.xxx: + - added loading csv + - added 1d, column, row basic stats +""" + +__author__ = ( + "Arthur Lu ", + "Jacob Levine ", +) + +__all__ = [ + 'load_csv', + 'basic_stats', + 'z_score', + 'z_normalize', + 'histo_analysis', + 'regression', + 'elo', + 'glicko2', + 'trueskill', + 'RegressionMetrics', + 'ClassificationMetrics', + 'kmeans', + 'pca', + 'decisiontree', + 'knn_classifier', + 'knn_regressor', + 'NaiveBayes', + 'SVM', + 'random_forest_classifier', + 'random_forest_regressor', + 'Glicko2', + # all statistics functions left out due to integration in other functions +] + +# now back to your regularly scheduled programming: + +# imports (now in alphabetical order! v 1.0.3.006): + +import csv +import numba +from numba import jit +import numpy as np +import scipy +from scipy import * +import sklearn +from sklearn import * +from analysis import trueskill as Trueskill + +class error(ValueError): + pass + +def load_csv(filepath): + with open(filepath, newline='') as csvfile: + file_array = np.array(list(csv.reader(csvfile))) + csvfile.close() + return file_array + +# expects 1d array +@jit(forceobj=True) +def basic_stats(data): + + data_t = np.array(data).astype(float) + + _mean = mean(data_t) + _median = median(data_t) + _stdev = stdev(data_t) + _variance = variance(data_t) + _min = npmin(data_t) + _max = npmax(data_t) + + return _mean, _median, _stdev, _variance, _min, _max + +# returns z score with inputs of point, mean and standard deviation of spread +@jit(forceobj=True) +def z_score(point, mean, stdev): + score = (point - mean) / stdev + + return score + +# expects 2d array, normalizes across all axes +@jit(forceobj=True) +def z_normalize(array, *args): + + array = np.array(array) + for arg in args: + array = sklearn.preprocessing.normalize(array, axis = arg) + + return array + +@jit(forceobj=True) +# expects 2d array of [x,y] +def histo_analysis(hist_data): + + if(len(hist_data[0]) > 2): + + hist_data = np.array(hist_data) + derivative = np.array(len(hist_data) - 1, dtype = float) + t = np.diff(hist_data) + derivative = t[1] / t[0] + np.sort(derivative) + + return basic_stats(derivative)[0], basic_stats(derivative)[3] + + else: + + return None + +def regression(inputs, outputs, args): # inputs, outputs expects N-D array + + X = np.array(inputs) + y = np.array(outputs) + + regressions = [] + + if 'lin' in args: # formula: ax + b + + try: + + def func(x, a, b): + + return a * x + b + + popt, pcov = scipy.optimize.curve_fit(func, X, y) + + regressions.append((popt.flatten().tolist(), None)) + + except Exception as e: + + pass + + if 'log' in args: # formula: a log (b(x + c)) + d + + try: + + def func(x, a, b, c, d): + + return a * np.log(b*(x + c)) + d + + popt, pcov = scipy.optimize.curve_fit(func, X, y) + + regressions.append((popt.flatten().tolist(), None)) + + except Exception as e: + + pass + + if 'exp' in args: # formula: a e ^ (b(x + c)) + d + + try: + + def func(x, a, b, c, d): + + return a * np.exp(b*(x + c)) + d + + popt, pcov = scipy.optimize.curve_fit(func, X, y) + + regressions.append((popt.flatten().tolist(), None)) + + except Exception as e: + + pass + + if 'ply' in args: # formula: a + bx^1 + cx^2 + dx^3 + ... + + inputs = np.array([inputs]) + outputs = np.array([outputs]) + + plys = [] + limit = len(outputs[0]) + + for i in range(2, limit): + + model = sklearn.preprocessing.PolynomialFeatures(degree = i) + model = sklearn.pipeline.make_pipeline(model, sklearn.linear_model.LinearRegression()) + model = model.fit(np.rot90(inputs), np.rot90(outputs)) + + params = model.steps[1][1].intercept_.tolist() + params = np.append(params, model.steps[1][1].coef_[0].tolist()[1::]) + params.flatten() + params = params.tolist() + + plys.append(params) + + regressions.append(plys) + + if 'sig' in args: # formula: a tanh (b(x + c)) + d + + try: + + def func(x, a, b, c, d): + + return a * np.tanh(b*(x + c)) + d + + popt, pcov = scipy.optimize.curve_fit(func, X, y) + + regressions.append((popt.flatten().tolist(), None)) + + except Exception as e: + + pass + + return regressions + +def elo(starting_score, opposing_score, observed, N, K): + + expected = 1/(1+10**((np.array(opposing_score) - starting_score)/N)) + + return starting_score + K*(np.sum(observed) - np.sum(expected)) + +def glicko2(starting_score, starting_rd, starting_vol, opposing_score, opposing_rd, observations): + + player = Glicko2(rating = starting_score, rd = starting_rd, vol = starting_vol) + + player.update_player([x for x in opposing_score], [x for x in opposing_rd], observations) + + return (player.rating, player.rd, player.vol) + +def trueskill(teams_data, observations): # teams_data is array of array of tuples ie. [[(mu, sigma), (mu, sigma), (mu, sigma)], [(mu, sigma), (mu, sigma), (mu, sigma)]] + + team_ratings = [] + + for team in teams_data: + team_temp = [] + for player in team: + player = Trueskill.Rating(player[0], player[1]) + team_temp.append(player) + team_ratings.append(team_temp) + + return Trueskill.rate(teams_data, observations) + +class RegressionMetrics(): + + def __new__(cls, predictions, targets): + + return cls.r_squared(cls, predictions, targets), cls.mse(cls, predictions, targets), cls.rms(cls, predictions, targets) + + def r_squared(self, predictions, targets): # assumes equal size inputs + + return sklearn.metrics.r2_score(targets, predictions) + + def mse(self, predictions, targets): + + return sklearn.metrics.mean_squared_error(targets, predictions) + + def rms(self, predictions, targets): + + return math.sqrt(sklearn.metrics.mean_squared_error(targets, predictions)) + +class ClassificationMetrics(): + + def __new__(cls, predictions, targets): + + return cls.cm(cls, predictions, targets), cls.cr(cls, predictions, targets) + + def cm(self, predictions, targets): + + return sklearn.metrics.confusion_matrix(targets, predictions) + + def cr(self, predictions, targets): + + return sklearn.metrics.classification_report(targets, predictions) + +@jit(nopython=True) +def mean(data): + + return np.mean(data) + +@jit(nopython=True) +def median(data): + + return np.median(data) + +@jit(nopython=True) +def stdev(data): + + return np.std(data) + +@jit(nopython=True) +def variance(data): + + return np.var(data) + +@jit(nopython=True) +def npmin(data): + + return np.amin(data) + +@jit(nopython=True) +def npmax(data): + + return np.amax(data) + +@jit(forceobj=True) +def kmeans(data, n_clusters=8, init="k-means++", n_init=10, max_iter=300, tol=0.0001, precompute_distances="auto", verbose=0, random_state=None, copy_x=True, n_jobs=None, algorithm="auto"): + + kernel = sklearn.cluster.KMeans(n_clusters = n_clusters, init = init, n_init = n_init, max_iter = max_iter, tol = tol, precompute_distances = precompute_distances, verbose = verbose, random_state = random_state, copy_x = copy_x, n_jobs = n_jobs, algorithm = algorithm) + kernel.fit(data) + predictions = kernel.predict(data) + centers = kernel.cluster_centers_ + + return centers, predictions + +@jit(forceobj=True) +def pca(data, n_components = None, copy = True, whiten = False, svd_solver = "auto", tol = 0.0, iterated_power = "auto", random_state = None): + + kernel = sklearn.decomposition.PCA(n_components = n_components, copy = copy, whiten = whiten, svd_solver = svd_solver, tol = tol, iterated_power = iterated_power, random_state = random_state) + + return kernel.fit_transform(data) + +@jit(forceobj=True) +def decisiontree(data, labels, test_size = 0.3, criterion = "gini", splitter = "default", max_depth = None): #expects *2d data and 1d labels + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.tree.DecisionTreeClassifier(criterion = criterion, splitter = splitter, max_depth = max_depth) + model = model.fit(data_train,labels_train) + predictions = model.predict(data_test) + metrics = ClassificationMetrics(predictions, labels_test) + + return model, metrics + +@jit(forceobj=True) +def knn_classifier(data, labels, test_size = 0.3, algorithm='auto', leaf_size=30, metric='minkowski', metric_params=None, n_jobs=None, n_neighbors=5, p=2, weights='uniform'): #expects *2d data and 1d labels post-scaling + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.neighbors.KNeighborsClassifier() + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + +def knn_regressor(data, outputs, test_size, n_neighbors = 5, weights = "uniform", algorithm = "auto", leaf_size = 30, p = 2, metric = "minkowski", metric_params = None, n_jobs = None): + + data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) + model = sklearn.neighbors.KNeighborsRegressor(n_neighbors = n_neighbors, weights = weights, algorithm = algorithm, leaf_size = leaf_size, p = p, metric = metric, metric_params = metric_params, n_jobs = n_jobs) + model.fit(data_train, outputs_train) + predictions = model.predict(data_test) + + return model, RegressionMetrics(predictions, outputs_test) + +class NaiveBayes: + + def guassian(self, data, labels, test_size = 0.3, priors = None, var_smoothing = 1e-09): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.GaussianNB(priors = priors, var_smoothing = var_smoothing) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + + def multinomial(self, data, labels, test_size = 0.3, alpha=1.0, fit_prior=True, class_prior=None): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.MultinomialNB(alpha = alpha, fit_prior = fit_prior, class_prior = class_prior) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + + def bernoulli(self, data, labels, test_size = 0.3, alpha=1.0, binarize=0.0, fit_prior=True, class_prior=None): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.BernoulliNB(alpha = alpha, binarize = binarize, fit_prior = fit_prior, class_prior = class_prior) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + + def complement(self, data, labels, test_size = 0.3, alpha=1.0, fit_prior=True, class_prior=None, norm=False): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + model = sklearn.naive_bayes.ComplementNB(alpha = alpha, fit_prior = fit_prior, class_prior = class_prior, norm = norm) + model.fit(data_train, labels_train) + predictions = model.predict(data_test) + + return model, ClassificationMetrics(predictions, labels_test) + +class SVM: + + class CustomKernel: + + def __new__(cls, C, kernel, degre, gamma, coef0, shrinking, probability, tol, cache_size, class_weight, verbose, max_iter, decision_function_shape, random_state): + + return sklearn.svm.SVC(C = C, kernel = kernel, gamma = gamma, coef0 = coef0, shrinking = shrinking, probability = probability, tol = tol, cache_size = cache_size, class_weight = class_weight, verbose = verbose, max_iter = max_iter, decision_function_shape = decision_function_shape, random_state = random_state) + + class StandardKernel: + + def __new__(cls, kernel, C=1.0, degree=3, gamma='auto_deprecated', coef0=0.0, shrinking=True, probability=False, tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape='ovr', random_state=None): + + return sklearn.svm.SVC(C = C, kernel = kernel, gamma = gamma, coef0 = coef0, shrinking = shrinking, probability = probability, tol = tol, cache_size = cache_size, class_weight = class_weight, verbose = verbose, max_iter = max_iter, decision_function_shape = decision_function_shape, random_state = random_state) + + class PrebuiltKernel: + + class Linear: + + def __new__(cls): + + return sklearn.svm.SVC(kernel = 'linear') + + class Polynomial: + + def __new__(cls, power, r_bias): + + return sklearn.svm.SVC(kernel = 'polynomial', degree = power, coef0 = r_bias) + + class RBF: + + def __new__(cls, gamma): + + return sklearn.svm.SVC(kernel = 'rbf', gamma = gamma) + + class Sigmoid: + + def __new__(cls, r_bias): + + return sklearn.svm.SVC(kernel = 'sigmoid', coef0 = r_bias) + + def fit(self, kernel, train_data, train_outputs): # expects *2d data, 1d labels or outputs + + return kernel.fit(train_data, train_outputs) + + def eval_classification(self, kernel, test_data, test_outputs): + + predictions = kernel.predict(test_data) + + return ClassificationMetrics(predictions, test_outputs) + + def eval_regression(self, kernel, test_data, test_outputs): + + predictions = kernel.predict(test_data) + + return RegressionMetrics(predictions, test_outputs) + +def random_forest_classifier(data, labels, test_size, n_estimators="warn", criterion="gini", max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features="auto", max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False, class_weight=None): + + data_train, data_test, labels_train, labels_test = sklearn.model_selection.train_test_split(data, labels, test_size=test_size, random_state=1) + kernel = sklearn.ensemble.RandomForestClassifier(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_samples_leaf = min_samples_leaf, min_weight_fraction_leaf = min_weight_fraction_leaf, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start, class_weight = class_weight) + kernel.fit(data_train, labels_train) + predictions = kernel.predict(data_test) + + return kernel, ClassificationMetrics(predictions, labels_test) + +def random_forest_regressor(data, outputs, test_size, n_estimators="warn", criterion="mse", max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features="auto", max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, bootstrap=True, oob_score=False, n_jobs=None, random_state=None, verbose=0, warm_start=False): + + data_train, data_test, outputs_train, outputs_test = sklearn.model_selection.train_test_split(data, outputs, test_size=test_size, random_state=1) + kernel = sklearn.ensemble.RandomForestRegressor(n_estimators = n_estimators, criterion = criterion, max_depth = max_depth, min_samples_split = min_samples_split, min_weight_fraction_leaf = min_weight_fraction_leaf, max_features = max_features, max_leaf_nodes = max_leaf_nodes, min_impurity_decrease = min_impurity_decrease, min_impurity_split = min_impurity_split, bootstrap = bootstrap, oob_score = oob_score, n_jobs = n_jobs, random_state = random_state, verbose = verbose, warm_start = warm_start) + kernel.fit(data_train, outputs_train) + predictions = kernel.predict(data_test) + + return kernel, RegressionMetrics(predictions, outputs_test) + +class Glicko2: + + _tau = 0.5 + + def getRating(self): + return (self.__rating * 173.7178) + 1500 + + def setRating(self, rating): + self.__rating = (rating - 1500) / 173.7178 + + rating = property(getRating, setRating) + + def getRd(self): + return self.__rd * 173.7178 + + def setRd(self, rd): + self.__rd = rd / 173.7178 + + rd = property(getRd, setRd) + + def __init__(self, rating = 1500, rd = 350, vol = 0.06): + + self.setRating(rating) + self.setRd(rd) + self.vol = vol + + def _preRatingRD(self): + + self.__rd = math.sqrt(math.pow(self.__rd, 2) + math.pow(self.vol, 2)) + + def update_player(self, rating_list, RD_list, outcome_list): + + rating_list = [(x - 1500) / 173.7178 for x in rating_list] + RD_list = [x / 173.7178 for x in RD_list] + + v = self._v(rating_list, RD_list) + self.vol = self._newVol(rating_list, RD_list, outcome_list, v) + self._preRatingRD() + + self.__rd = 1 / math.sqrt((1 / math.pow(self.__rd, 2)) + (1 / v)) + + tempSum = 0 + for i in range(len(rating_list)): + tempSum += self._g(RD_list[i]) * \ + (outcome_list[i] - self._E(rating_list[i], RD_list[i])) + self.__rating += math.pow(self.__rd, 2) * tempSum + + + def _newVol(self, rating_list, RD_list, outcome_list, v): + + i = 0 + delta = self._delta(rating_list, RD_list, outcome_list, v) + a = math.log(math.pow(self.vol, 2)) + tau = self._tau + x0 = a + x1 = 0 + + while x0 != x1: + # New iteration, so x(i) becomes x(i-1) + x0 = x1 + d = math.pow(self.__rating, 2) + v + math.exp(x0) + h1 = -(x0 - a) / math.pow(tau, 2) - 0.5 * math.exp(x0) \ + / d + 0.5 * math.exp(x0) * math.pow(delta / d, 2) + h2 = -1 / math.pow(tau, 2) - 0.5 * math.exp(x0) * \ + (math.pow(self.__rating, 2) + v) \ + / math.pow(d, 2) + 0.5 * math.pow(delta, 2) * math.exp(x0) \ + * (math.pow(self.__rating, 2) + v - math.exp(x0)) / math.pow(d, 3) + x1 = x0 - (h1 / h2) + + return math.exp(x1 / 2) + + def _delta(self, rating_list, RD_list, outcome_list, v): + + tempSum = 0 + for i in range(len(rating_list)): + tempSum += self._g(RD_list[i]) * (outcome_list[i] - self._E(rating_list[i], RD_list[i])) + return v * tempSum + + def _v(self, rating_list, RD_list): + + tempSum = 0 + for i in range(len(rating_list)): + tempE = self._E(rating_list[i], RD_list[i]) + tempSum += math.pow(self._g(RD_list[i]), 2) * tempE * (1 - tempE) + return 1 / tempSum + + def _E(self, p2rating, p2RD): + + return 1 / (1 + math.exp(-1 * self._g(p2RD) * \ + (self.__rating - p2rating))) + + def _g(self, RD): + + return 1 / math.sqrt(1 + 3 * math.pow(RD, 2) / math.pow(math.pi, 2)) + + def did_not_compete(self): + + self._preRatingRD() diff --git a/analysis-master/build/lib/analysis/regression.py b/analysis-master/build/lib/analysis/regression.py new file mode 100644 index 00000000..e899e9ff --- /dev/null +++ b/analysis-master/build/lib/analysis/regression.py @@ -0,0 +1,220 @@ +# Titan Robotics Team 2022: CUDA-based Regressions Module +# Written by Arthur Lu & Jacob Levine +# Notes: +# this module has been automatically inegrated into analysis.py, and should be callable as a class from the package +# this module is cuda-optimized and vectorized (except for one small part) +# setup: + +__version__ = "1.0.0.004" + +# changelog should be viewed using print(analysis.regression.__changelog__) +__changelog__ = """ + 1.0.0.004: + - bug fixes + - fixed changelog + 1.0.0.003: + - bug fixes + 1.0.0.002: + -Added more parameters to log, exponential, polynomial + -Added SigmoidalRegKernelArthur, because Arthur apparently needs + to train the scaling and shifting of sigmoids + 1.0.0.001: + -initial release, with linear, log, exponential, polynomial, and sigmoid kernels + -already vectorized (except for polynomial generation) and CUDA-optimized +""" + +__author__ = ( + "Jacob Levine ", + "Arthur Lu " +) + +__all__ = [ + 'factorial', + 'take_all_pwrs', + 'num_poly_terms', + 'set_device', + 'LinearRegKernel', + 'SigmoidalRegKernel', + 'LogRegKernel', + 'PolyRegKernel', + 'ExpRegKernel', + 'SigmoidalRegKernelArthur', + 'SGDTrain', + 'CustomTrain' +] + +import torch + +global device + +device = "cuda:0" if torch.torch.cuda.is_available() else "cpu" + +#todo: document completely + +def set_device(self, new_device): + device=new_device + +class LinearRegKernel(): + parameters= [] + weights=None + bias=None + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.bias] + def forward(self,mtx): + long_bias=self.bias.repeat([1,mtx.size()[1]]) + return torch.matmul(self.weights,mtx)+long_bias + +class SigmoidalRegKernel(): + parameters= [] + weights=None + bias=None + sigmoid=torch.nn.Sigmoid() + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.bias] + def forward(self,mtx): + long_bias=self.bias.repeat([1,mtx.size()[1]]) + return self.sigmoid(torch.matmul(self.weights,mtx)+long_bias) + +class SigmoidalRegKernelArthur(): + parameters= [] + weights=None + in_bias=None + scal_mult=None + out_bias=None + sigmoid=torch.nn.Sigmoid() + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.in_bias=torch.rand(1, requires_grad=True, device=device) + self.scal_mult=torch.rand(1, requires_grad=True, device=device) + self.out_bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] + def forward(self,mtx): + long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) + long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) + return (self.scal_mult*self.sigmoid(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias + +class LogRegKernel(): + parameters= [] + weights=None + in_bias=None + scal_mult=None + out_bias=None + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.in_bias=torch.rand(1, requires_grad=True, device=device) + self.scal_mult=torch.rand(1, requires_grad=True, device=device) + self.out_bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] + def forward(self,mtx): + long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) + long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) + return (self.scal_mult*torch.log(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias + +class ExpRegKernel(): + parameters= [] + weights=None + in_bias=None + scal_mult=None + out_bias=None + def __init__(self, num_vars): + self.weights=torch.rand(num_vars, requires_grad=True, device=device) + self.in_bias=torch.rand(1, requires_grad=True, device=device) + self.scal_mult=torch.rand(1, requires_grad=True, device=device) + self.out_bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.in_bias, self.scal_mult, self.out_bias] + def forward(self,mtx): + long_in_bias=self.in_bias.repeat([1,mtx.size()[1]]) + long_out_bias=self.out_bias.repeat([1,mtx.size()[1]]) + return (self.scal_mult*torch.exp(torch.matmul(self.weights,mtx)+long_in_bias))+long_out_bias + +class PolyRegKernel(): + parameters= [] + weights=None + bias=None + power=None + def __init__(self, num_vars, power): + self.power=power + num_terms=self.num_poly_terms(num_vars, power) + self.weights=torch.rand(num_terms, requires_grad=True, device=device) + self.bias=torch.rand(1, requires_grad=True, device=device) + self.parameters=[self.weights,self.bias] + def num_poly_terms(self,num_vars, power): + if power == 0: + return 0 + return int(self.factorial(num_vars+power-1) / self.factorial(power) / self.factorial(num_vars-1)) + self.num_poly_terms(num_vars, power-1) + def factorial(self,n): + if n==0: + return 1 + else: + return n*self.factorial(n-1) + def take_all_pwrs(self, vec, pwr): + #todo: vectorize (kinda) + combins=torch.combinations(vec, r=pwr, with_replacement=True) + out=torch.ones(combins.size()[0]).to(device).to(torch.float) + for i in torch.t(combins).to(device).to(torch.float): + out *= i + if pwr == 1: + return out + else: + return torch.cat((out,self.take_all_pwrs(vec, pwr-1))) + def forward(self,mtx): + #TODO: Vectorize the last part + cols=[] + for i in torch.t(mtx): + cols.append(self.take_all_pwrs(i,self.power)) + new_mtx=torch.t(torch.stack(cols)) + long_bias=self.bias.repeat([1,mtx.size()[1]]) + return torch.matmul(self.weights,new_mtx)+long_bias + +def SGDTrain(self, kernel, data, ground, loss=torch.nn.MSELoss(), iterations=1000, learning_rate=.1, return_losses=False): + optim=torch.optim.SGD(kernel.parameters, lr=learning_rate) + data_cuda=data.to(device) + ground_cuda=ground.to(device) + if (return_losses): + losses=[] + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data_cuda) + ls=loss(pred,ground_cuda) + losses.append(ls.item()) + ls.backward() + optim.step() + return [kernel,losses] + else: + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data_cuda) + ls=loss(pred,ground_cuda) + ls.backward() + optim.step() + return kernel + +def CustomTrain(self, kernel, optim, data, ground, loss=torch.nn.MSELoss(), iterations=1000, return_losses=False): + data_cuda=data.to(device) + ground_cuda=ground.to(device) + if (return_losses): + losses=[] + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data) + ls=loss(pred,ground) + losses.append(ls.item()) + ls.backward() + optim.step() + return [kernel,losses] + else: + for i in range(iterations): + with torch.set_grad_enabled(True): + optim.zero_grad() + pred=kernel.forward(data_cuda) + ls=loss(pred,ground_cuda) + ls.backward() + optim.step() + return kernel \ No newline at end of file diff --git a/analysis-master/build/lib/analysis/titanlearn.py b/analysis-master/build/lib/analysis/titanlearn.py new file mode 100644 index 00000000..b69d36e3 --- /dev/null +++ b/analysis-master/build/lib/analysis/titanlearn.py @@ -0,0 +1,122 @@ +# Titan Robotics Team 2022: ML Module +# Written by Arthur Lu & Jacob Levine +# Notes: +# this should be imported as a python module using 'import titanlearn' +# this should be included in the local directory or environment variable +# this module is optimized for multhreaded computing +# this module learns from its mistakes far faster than 2022's captains +# setup: + +__version__ = "2.0.1.001" + +#changelog should be viewed using print(analysis.__changelog__) +__changelog__ = """changelog: + 2.0.1.001: + - removed matplotlib import + - removed graphloss() + 2.0.1.000: + - added net, dataset, dataloader, and stdtrain template definitions + - added graphloss function + 2.0.0.001: + - added clear functions + 2.0.0.000: + - complete rewrite planned + - depreciated 1.0.0.xxx versions + - added simple training loop + 1.0.0.xxx: + -added generation of ANNS, basic SGD training +""" + +__author__ = ( + "Arthur Lu ," + "Jacob Levine ," + ) + +__all__ = [ + 'clear', + 'net', + 'dataset', + 'dataloader', + 'train', + 'stdtrainer', + ] + +import torch +from os import system, name +import numpy as np + +def clear(): + if name == 'nt': + _ = system('cls') + else: + _ = system('clear') + +class net(torch.nn.Module): #template for standard neural net + def __init__(self): + super(Net, self).__init__() + + def forward(self, input): + pass + +class dataset(torch.utils.data.Dataset): #template for standard dataset + + def __init__(self): + super(torch.utils.data.Dataset).__init__() + + def __getitem__(self, index): + pass + + def __len__(self): + pass + +def dataloader(dataset, batch_size, num_workers, shuffle = True): + + return torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=num_workers) + +def train(device, net, epochs, trainloader, optimizer, criterion): #expects standard dataloader, whch returns (inputs, labels) + + dataset_len = trainloader.dataset.__len__() + iter_count = 0 + running_loss = 0 + running_loss_list = [] + + for epoch in range(epochs): # loop over the dataset multiple times + + for i, data in enumerate(trainloader, 0): + + inputs = data[0].to(device) + labels = data[1].to(device) + + optimizer.zero_grad() + + outputs = net(inputs) + loss = criterion(outputs, labels.to(torch.float)) + + loss.backward() + optimizer.step() + + # monitoring steps below + + iter_count += 1 + running_loss += loss.item() + running_loss_list.append(running_loss) + clear() + + print("training on: " + device) + print("iteration: " + str(i) + "/" + str(int(dataset_len / trainloader.batch_size)) + " | " + "epoch: " + str(epoch) + "/" + str(epochs)) + print("current batch loss: " + str(loss.item)) + print("running loss: " + str(running_loss / iter_count)) + + return net, running_loss_list + print("finished training") + +def stdtrainer(net, criterion, optimizer, dataloader, epochs, batch_size): + + device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + + net = net.to(device) + criterion = criterion.to(device) + optimizer = optimizer.to(device) + trainloader = dataloader + + return train(device, net, epochs, trainloader, optimizer, criterion) \ No newline at end of file diff --git a/analysis-master/build/lib/analysis/trueskill.py b/analysis-master/build/lib/analysis/trueskill.py new file mode 100644 index 00000000..116357df --- /dev/null +++ b/analysis-master/build/lib/analysis/trueskill.py @@ -0,0 +1,907 @@ +from __future__ import absolute_import + +from itertools import chain +import math + +from six import iteritems +from six.moves import map, range, zip +from six import iterkeys + +import copy +try: + from numbers import Number +except ImportError: + Number = (int, long, float, complex) + +inf = float('inf') + +class Gaussian(object): + #: Precision, the inverse of the variance. + pi = 0 + #: Precision adjusted mean, the precision multiplied by the mean. + tau = 0 + + def __init__(self, mu=None, sigma=None, pi=0, tau=0): + if mu is not None: + if sigma is None: + raise TypeError('sigma argument is needed') + elif sigma == 0: + raise ValueError('sigma**2 should be greater than 0') + pi = sigma ** -2 + tau = pi * mu + self.pi = pi + self.tau = tau + + @property + def mu(self): + return self.pi and self.tau / self.pi + + @property + def sigma(self): + return math.sqrt(1 / self.pi) if self.pi else inf + + def __mul__(self, other): + pi, tau = self.pi + other.pi, self.tau + other.tau + return Gaussian(pi=pi, tau=tau) + + def __truediv__(self, other): + pi, tau = self.pi - other.pi, self.tau - other.tau + return Gaussian(pi=pi, tau=tau) + + __div__ = __truediv__ # for Python 2 + + def __eq__(self, other): + return self.pi == other.pi and self.tau == other.tau + + def __lt__(self, other): + return self.mu < other.mu + + def __le__(self, other): + return self.mu <= other.mu + + def __gt__(self, other): + return self.mu > other.mu + + def __ge__(self, other): + return self.mu >= other.mu + + def __repr__(self): + return 'N(mu={:.3f}, sigma={:.3f})'.format(self.mu, self.sigma) + + def _repr_latex_(self): + latex = r'\mathcal{{ N }}( {:.3f}, {:.3f}^2 )'.format(self.mu, self.sigma) + return '$%s$' % latex + +class Matrix(list): + def __init__(self, src, height=None, width=None): + if callable(src): + f, src = src, {} + size = [height, width] + if not height: + def set_height(height): + size[0] = height + size[0] = set_height + if not width: + def set_width(width): + size[1] = width + size[1] = set_width + try: + for (r, c), val in f(*size): + src[r, c] = val + except TypeError: + raise TypeError('A callable src must return an interable ' + 'which generates a tuple containing ' + 'coordinate and value') + height, width = tuple(size) + if height is None or width is None: + raise TypeError('A callable src must call set_height and ' + 'set_width if the size is non-deterministic') + if isinstance(src, list): + is_number = lambda x: isinstance(x, Number) + unique_col_sizes = set(map(len, src)) + everything_are_number = filter(is_number, sum(src, [])) + if len(unique_col_sizes) != 1 or not everything_are_number: + raise ValueError('src must be a rectangular array of numbers') + two_dimensional_array = src + elif isinstance(src, dict): + if not height or not width: + w = h = 0 + for r, c in iterkeys(src): + if not height: + h = max(h, r + 1) + if not width: + w = max(w, c + 1) + if not height: + height = h + if not width: + width = w + two_dimensional_array = [] + for r in range(height): + row = [] + two_dimensional_array.append(row) + for c in range(width): + row.append(src.get((r, c), 0)) + else: + raise TypeError('src must be a list or dict or callable') + super(Matrix, self).__init__(two_dimensional_array) + + @property + def height(self): + return len(self) + + @property + def width(self): + return len(self[0]) + + def transpose(self): + height, width = self.height, self.width + src = {} + for c in range(width): + for r in range(height): + src[c, r] = self[r][c] + return type(self)(src, height=width, width=height) + + def minor(self, row_n, col_n): + height, width = self.height, self.width + if not (0 <= row_n < height): + raise ValueError('row_n should be between 0 and %d' % height) + elif not (0 <= col_n < width): + raise ValueError('col_n should be between 0 and %d' % width) + two_dimensional_array = [] + for r in range(height): + if r == row_n: + continue + row = [] + two_dimensional_array.append(row) + for c in range(width): + if c == col_n: + continue + row.append(self[r][c]) + return type(self)(two_dimensional_array) + + def determinant(self): + height, width = self.height, self.width + if height != width: + raise ValueError('Only square matrix can calculate a determinant') + tmp, rv = copy.deepcopy(self), 1. + for c in range(width - 1, 0, -1): + pivot, r = max((abs(tmp[r][c]), r) for r in range(c + 1)) + pivot = tmp[r][c] + if not pivot: + return 0. + tmp[r], tmp[c] = tmp[c], tmp[r] + if r != c: + rv = -rv + rv *= pivot + fact = -1. / pivot + for r in range(c): + f = fact * tmp[r][c] + for x in range(c): + tmp[r][x] += f * tmp[c][x] + return rv * tmp[0][0] + + def adjugate(self): + height, width = self.height, self.width + if height != width: + raise ValueError('Only square matrix can be adjugated') + if height == 2: + a, b = self[0][0], self[0][1] + c, d = self[1][0], self[1][1] + return type(self)([[d, -b], [-c, a]]) + src = {} + for r in range(height): + for c in range(width): + sign = -1 if (r + c) % 2 else 1 + src[r, c] = self.minor(r, c).determinant() * sign + return type(self)(src, height, width) + + def inverse(self): + if self.height == self.width == 1: + return type(self)([[1. / self[0][0]]]) + return (1. / self.determinant()) * self.adjugate() + + def __add__(self, other): + height, width = self.height, self.width + if (height, width) != (other.height, other.width): + raise ValueError('Must be same size') + src = {} + for r in range(height): + for c in range(width): + src[r, c] = self[r][c] + other[r][c] + return type(self)(src, height, width) + + def __mul__(self, other): + if self.width != other.height: + raise ValueError('Bad size') + height, width = self.height, other.width + src = {} + for r in range(height): + for c in range(width): + src[r, c] = sum(self[r][x] * other[x][c] + for x in range(self.width)) + return type(self)(src, height, width) + + def __rmul__(self, other): + if not isinstance(other, Number): + raise TypeError('The operand should be a number') + height, width = self.height, self.width + src = {} + for r in range(height): + for c in range(width): + src[r, c] = other * self[r][c] + return type(self)(src, height, width) + + def __repr__(self): + return '{}({})'.format(type(self).__name__, super(Matrix, self).__repr__()) + + def _repr_latex_(self): + rows = [' && '.join(['%.3f' % cell for cell in row]) for row in self] + latex = r'\begin{matrix} %s \end{matrix}' % r'\\'.join(rows) + return '$%s$' % latex + +def _gen_erfcinv(erfc, math=math): + def erfcinv(y): + """The inverse function of erfc.""" + if y >= 2: + return -100. + elif y <= 0: + return 100. + zero_point = y < 1 + if not zero_point: + y = 2 - y + t = math.sqrt(-2 * math.log(y / 2.)) + x = -0.70711 * \ + ((2.30753 + t * 0.27061) / (1. + t * (0.99229 + t * 0.04481)) - t) + for i in range(2): + err = erfc(x) - y + x += err / (1.12837916709551257 * math.exp(-(x ** 2)) - x * err) + return x if zero_point else -x + return erfcinv + +def _gen_ppf(erfc, math=math): + erfcinv = _gen_erfcinv(erfc, math) + def ppf(x, mu=0, sigma=1): + return mu - sigma * math.sqrt(2) * erfcinv(2 * x) + return ppf + +def erfc(x): + z = abs(x) + t = 1. / (1. + z / 2.) + r = t * math.exp(-z * z - 1.26551223 + t * (1.00002368 + t * ( + 0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * ( + 0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * ( + -0.82215223 + t * 0.17087277 + ))) + ))) + ))) + return 2. - r if x < 0 else r + +def cdf(x, mu=0, sigma=1): + return 0.5 * erfc(-(x - mu) / (sigma * math.sqrt(2))) + + +def pdf(x, mu=0, sigma=1): + return (1 / math.sqrt(2 * math.pi) * abs(sigma) * + math.exp(-(((x - mu) / abs(sigma)) ** 2 / 2))) + +ppf = _gen_ppf(erfc) + +def choose_backend(backend): + if backend is None: # fallback + return cdf, pdf, ppf + elif backend == 'mpmath': + try: + import mpmath + except ImportError: + raise ImportError('Install "mpmath" to use this backend') + return mpmath.ncdf, mpmath.npdf, _gen_ppf(mpmath.erfc, math=mpmath) + elif backend == 'scipy': + try: + from scipy.stats import norm + except ImportError: + raise ImportError('Install "scipy" to use this backend') + return norm.cdf, norm.pdf, norm.ppf + raise ValueError('%r backend is not defined' % backend) + +def available_backends(): + backends = [None] + for backend in ['mpmath', 'scipy']: + try: + __import__(backend) + except ImportError: + continue + backends.append(backend) + return backends + +class Node(object): + + pass + +class Variable(Node, Gaussian): + + def __init__(self): + self.messages = {} + super(Variable, self).__init__() + + def set(self, val): + delta = self.delta(val) + self.pi, self.tau = val.pi, val.tau + return delta + + def delta(self, other): + pi_delta = abs(self.pi - other.pi) + if pi_delta == inf: + return 0. + return max(abs(self.tau - other.tau), math.sqrt(pi_delta)) + + def update_message(self, factor, pi=0, tau=0, message=None): + message = message or Gaussian(pi=pi, tau=tau) + old_message, self[factor] = self[factor], message + return self.set(self / old_message * message) + + def update_value(self, factor, pi=0, tau=0, value=None): + value = value or Gaussian(pi=pi, tau=tau) + old_message = self[factor] + self[factor] = value * old_message / self + return self.set(value) + + def __getitem__(self, factor): + return self.messages[factor] + + def __setitem__(self, factor, message): + self.messages[factor] = message + + def __repr__(self): + args = (type(self).__name__, super(Variable, self).__repr__(), + len(self.messages), '' if len(self.messages) == 1 else 's') + return '<%s %s with %d connection%s>' % args + + +class Factor(Node): + + def __init__(self, variables): + self.vars = variables + for var in variables: + var[self] = Gaussian() + + def down(self): + return 0 + + def up(self): + return 0 + + @property + def var(self): + assert len(self.vars) == 1 + return self.vars[0] + + def __repr__(self): + args = (type(self).__name__, len(self.vars), + '' if len(self.vars) == 1 else 's') + return '<%s with %d connection%s>' % args + + +class PriorFactor(Factor): + + def __init__(self, var, val, dynamic=0): + super(PriorFactor, self).__init__([var]) + self.val = val + self.dynamic = dynamic + + def down(self): + sigma = math.sqrt(self.val.sigma ** 2 + self.dynamic ** 2) + value = Gaussian(self.val.mu, sigma) + return self.var.update_value(self, value=value) + + +class LikelihoodFactor(Factor): + + def __init__(self, mean_var, value_var, variance): + super(LikelihoodFactor, self).__init__([mean_var, value_var]) + self.mean = mean_var + self.value = value_var + self.variance = variance + + def calc_a(self, var): + return 1. / (1. + self.variance * var.pi) + + def down(self): + # update value. + msg = self.mean / self.mean[self] + a = self.calc_a(msg) + return self.value.update_message(self, a * msg.pi, a * msg.tau) + + def up(self): + # update mean. + msg = self.value / self.value[self] + a = self.calc_a(msg) + return self.mean.update_message(self, a * msg.pi, a * msg.tau) + + +class SumFactor(Factor): + + def __init__(self, sum_var, term_vars, coeffs): + super(SumFactor, self).__init__([sum_var] + term_vars) + self.sum = sum_var + self.terms = term_vars + self.coeffs = coeffs + + def down(self): + vals = self.terms + msgs = [var[self] for var in vals] + return self.update(self.sum, vals, msgs, self.coeffs) + + def up(self, index=0): + coeff = self.coeffs[index] + coeffs = [] + for x, c in enumerate(self.coeffs): + try: + if x == index: + coeffs.append(1. / coeff) + else: + coeffs.append(-c / coeff) + except ZeroDivisionError: + coeffs.append(0.) + vals = self.terms[:] + vals[index] = self.sum + msgs = [var[self] for var in vals] + return self.update(self.terms[index], vals, msgs, coeffs) + + def update(self, var, vals, msgs, coeffs): + pi_inv = 0 + mu = 0 + for val, msg, coeff in zip(vals, msgs, coeffs): + div = val / msg + mu += coeff * div.mu + if pi_inv == inf: + continue + try: + # numpy.float64 handles floating-point error by different way. + # For example, it can just warn RuntimeWarning on n/0 problem + # instead of throwing ZeroDivisionError. So div.pi, the + # denominator has to be a built-in float. + pi_inv += coeff ** 2 / float(div.pi) + except ZeroDivisionError: + pi_inv = inf + pi = 1. / pi_inv + tau = pi * mu + return var.update_message(self, pi, tau) + + +class TruncateFactor(Factor): + + def __init__(self, var, v_func, w_func, draw_margin): + super(TruncateFactor, self).__init__([var]) + self.v_func = v_func + self.w_func = w_func + self.draw_margin = draw_margin + + def up(self): + val = self.var + msg = self.var[self] + div = val / msg + sqrt_pi = math.sqrt(div.pi) + args = (div.tau / sqrt_pi, self.draw_margin * sqrt_pi) + v = self.v_func(*args) + w = self.w_func(*args) + denom = (1. - w) + pi, tau = div.pi / denom, (div.tau + sqrt_pi * v) / denom + return val.update_value(self, pi, tau) + +#: Default initial mean of ratings. +MU = 25. +#: Default initial standard deviation of ratings. +SIGMA = MU / 3 +#: Default distance that guarantees about 76% chance of winning. +BETA = SIGMA / 2 +#: Default dynamic factor. +TAU = SIGMA / 100 +#: Default draw probability of the game. +DRAW_PROBABILITY = .10 +#: A basis to check reliability of the result. +DELTA = 0.0001 + + +def calc_draw_probability(draw_margin, size, env=None): + if env is None: + env = global_env() + return 2 * env.cdf(draw_margin / (math.sqrt(size) * env.beta)) - 1 + + +def calc_draw_margin(draw_probability, size, env=None): + if env is None: + env = global_env() + return env.ppf((draw_probability + 1) / 2.) * math.sqrt(size) * env.beta + + +def _team_sizes(rating_groups): + team_sizes = [0] + for group in rating_groups: + team_sizes.append(len(group) + team_sizes[-1]) + del team_sizes[0] + return team_sizes + + +def _floating_point_error(env): + if env.backend == 'mpmath': + msg = 'Set "mpmath.mp.dps" to higher' + else: + msg = 'Cannot calculate correctly, set backend to "mpmath"' + return FloatingPointError(msg) + + +class Rating(Gaussian): + def __init__(self, mu=None, sigma=None): + if isinstance(mu, tuple): + mu, sigma = mu + elif isinstance(mu, Gaussian): + mu, sigma = mu.mu, mu.sigma + if mu is None: + mu = global_env().mu + if sigma is None: + sigma = global_env().sigma + super(Rating, self).__init__(mu, sigma) + + def __int__(self): + return int(self.mu) + + def __long__(self): + return long(self.mu) + + def __float__(self): + return float(self.mu) + + def __iter__(self): + return iter((self.mu, self.sigma)) + + def __repr__(self): + c = type(self) + args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma) + return '%s(mu=%.3f, sigma=%.3f)' % args + + +class TrueSkill(object): + def __init__(self, mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, + draw_probability=DRAW_PROBABILITY, backend=None): + self.mu = mu + self.sigma = sigma + self.beta = beta + self.tau = tau + self.draw_probability = draw_probability + self.backend = backend + if isinstance(backend, tuple): + self.cdf, self.pdf, self.ppf = backend + else: + self.cdf, self.pdf, self.ppf = choose_backend(backend) + + def create_rating(self, mu=None, sigma=None): + if mu is None: + mu = self.mu + if sigma is None: + sigma = self.sigma + return Rating(mu, sigma) + + def v_win(self, diff, draw_margin): + x = diff - draw_margin + denom = self.cdf(x) + return (self.pdf(x) / denom) if denom else -x + + def v_draw(self, diff, draw_margin): + abs_diff = abs(diff) + a, b = draw_margin - abs_diff, -draw_margin - abs_diff + denom = self.cdf(a) - self.cdf(b) + numer = self.pdf(b) - self.pdf(a) + return ((numer / denom) if denom else a) * (-1 if diff < 0 else +1) + + def w_win(self, diff, draw_margin): + x = diff - draw_margin + v = self.v_win(diff, draw_margin) + w = v * (v + x) + if 0 < w < 1: + return w + raise _floating_point_error(self) + + def w_draw(self, diff, draw_margin): + abs_diff = abs(diff) + a, b = draw_margin - abs_diff, -draw_margin - abs_diff + denom = self.cdf(a) - self.cdf(b) + if not denom: + raise _floating_point_error(self) + v = self.v_draw(abs_diff, draw_margin) + return (v ** 2) + (a * self.pdf(a) - b * self.pdf(b)) / denom + + def validate_rating_groups(self, rating_groups): + # check group sizes + if len(rating_groups) < 2: + raise ValueError('Need multiple rating groups') + elif not all(rating_groups): + raise ValueError('Each group must contain multiple ratings') + # check group types + group_types = set(map(type, rating_groups)) + if len(group_types) != 1: + raise TypeError('All groups should be same type') + elif group_types.pop() is Rating: + raise TypeError('Rating cannot be a rating group') + # normalize rating_groups + if isinstance(rating_groups[0], dict): + dict_rating_groups = rating_groups + rating_groups = [] + keys = [] + for dict_rating_group in dict_rating_groups: + rating_group, key_group = [], [] + for key, rating in iteritems(dict_rating_group): + rating_group.append(rating) + key_group.append(key) + rating_groups.append(tuple(rating_group)) + keys.append(tuple(key_group)) + else: + rating_groups = list(rating_groups) + keys = None + return rating_groups, keys + + def validate_weights(self, weights, rating_groups, keys=None): + if weights is None: + weights = [(1,) * len(g) for g in rating_groups] + elif isinstance(weights, dict): + weights_dict, weights = weights, [] + for x, group in enumerate(rating_groups): + w = [] + weights.append(w) + for y, rating in enumerate(group): + if keys is not None: + y = keys[x][y] + w.append(weights_dict.get((x, y), 1)) + return weights + + def factor_graph_builders(self, rating_groups, ranks, weights): + flatten_ratings = sum(map(tuple, rating_groups), ()) + flatten_weights = sum(map(tuple, weights), ()) + size = len(flatten_ratings) + group_size = len(rating_groups) + # create variables + rating_vars = [Variable() for x in range(size)] + perf_vars = [Variable() for x in range(size)] + team_perf_vars = [Variable() for x in range(group_size)] + team_diff_vars = [Variable() for x in range(group_size - 1)] + team_sizes = _team_sizes(rating_groups) + # layer builders + def build_rating_layer(): + for rating_var, rating in zip(rating_vars, flatten_ratings): + yield PriorFactor(rating_var, rating, self.tau) + def build_perf_layer(): + for rating_var, perf_var in zip(rating_vars, perf_vars): + yield LikelihoodFactor(rating_var, perf_var, self.beta ** 2) + def build_team_perf_layer(): + for team, team_perf_var in enumerate(team_perf_vars): + if team > 0: + start = team_sizes[team - 1] + else: + start = 0 + end = team_sizes[team] + child_perf_vars = perf_vars[start:end] + coeffs = flatten_weights[start:end] + yield SumFactor(team_perf_var, child_perf_vars, coeffs) + def build_team_diff_layer(): + for team, team_diff_var in enumerate(team_diff_vars): + yield SumFactor(team_diff_var, + team_perf_vars[team:team + 2], [+1, -1]) + def build_trunc_layer(): + for x, team_diff_var in enumerate(team_diff_vars): + if callable(self.draw_probability): + # dynamic draw probability + team_perf1, team_perf2 = team_perf_vars[x:x + 2] + args = (Rating(team_perf1), Rating(team_perf2), self) + draw_probability = self.draw_probability(*args) + else: + # static draw probability + draw_probability = self.draw_probability + size = sum(map(len, rating_groups[x:x + 2])) + draw_margin = calc_draw_margin(draw_probability, size, self) + if ranks[x] == ranks[x + 1]: # is a tie? + v_func, w_func = self.v_draw, self.w_draw + else: + v_func, w_func = self.v_win, self.w_win + yield TruncateFactor(team_diff_var, + v_func, w_func, draw_margin) + # build layers + return (build_rating_layer, build_perf_layer, build_team_perf_layer, + build_team_diff_layer, build_trunc_layer) + + def run_schedule(self, build_rating_layer, build_perf_layer, + build_team_perf_layer, build_team_diff_layer, + build_trunc_layer, min_delta=DELTA): + if min_delta <= 0: + raise ValueError('min_delta must be greater than 0') + layers = [] + def build(builders): + layers_built = [list(build()) for build in builders] + layers.extend(layers_built) + return layers_built + # gray arrows + layers_built = build([build_rating_layer, + build_perf_layer, + build_team_perf_layer]) + rating_layer, perf_layer, team_perf_layer = layers_built + for f in chain(*layers_built): + f.down() + # arrow #1, #2, #3 + team_diff_layer, trunc_layer = build([build_team_diff_layer, + build_trunc_layer]) + team_diff_len = len(team_diff_layer) + for x in range(10): + if team_diff_len == 1: + # only two teams + team_diff_layer[0].down() + delta = trunc_layer[0].up() + else: + # multiple teams + delta = 0 + for x in range(team_diff_len - 1): + team_diff_layer[x].down() + delta = max(delta, trunc_layer[x].up()) + team_diff_layer[x].up(1) # up to right variable + for x in range(team_diff_len - 1, 0, -1): + team_diff_layer[x].down() + delta = max(delta, trunc_layer[x].up()) + team_diff_layer[x].up(0) # up to left variable + # repeat until to small update + if delta <= min_delta: + break + # up both ends + team_diff_layer[0].up(0) + team_diff_layer[team_diff_len - 1].up(1) + # up the remainder of the black arrows + for f in team_perf_layer: + for x in range(len(f.vars) - 1): + f.up(x) + for f in perf_layer: + f.up() + return layers + + def rate(self, rating_groups, ranks=None, weights=None, min_delta=DELTA): + rating_groups, keys = self.validate_rating_groups(rating_groups) + weights = self.validate_weights(weights, rating_groups, keys) + group_size = len(rating_groups) + if ranks is None: + ranks = range(group_size) + elif len(ranks) != group_size: + raise ValueError('Wrong ranks') + # sort rating groups by rank + by_rank = lambda x: x[1][1] + sorting = sorted(enumerate(zip(rating_groups, ranks, weights)), + key=by_rank) + sorted_rating_groups, sorted_ranks, sorted_weights = [], [], [] + for x, (g, r, w) in sorting: + sorted_rating_groups.append(g) + sorted_ranks.append(r) + # make weights to be greater than 0 + sorted_weights.append(max(min_delta, w_) for w_ in w) + # build factor graph + args = (sorted_rating_groups, sorted_ranks, sorted_weights) + builders = self.factor_graph_builders(*args) + args = builders + (min_delta,) + layers = self.run_schedule(*args) + # make result + rating_layer, team_sizes = layers[0], _team_sizes(sorted_rating_groups) + transformed_groups = [] + for start, end in zip([0] + team_sizes[:-1], team_sizes): + group = [] + for f in rating_layer[start:end]: + group.append(Rating(float(f.var.mu), float(f.var.sigma))) + transformed_groups.append(tuple(group)) + by_hint = lambda x: x[0] + unsorting = sorted(zip((x for x, __ in sorting), transformed_groups), + key=by_hint) + if keys is None: + return [g for x, g in unsorting] + # restore the structure with input dictionary keys + return [dict(zip(keys[x], g)) for x, g in unsorting] + + def quality(self, rating_groups, weights=None): + rating_groups, keys = self.validate_rating_groups(rating_groups) + weights = self.validate_weights(weights, rating_groups, keys) + flatten_ratings = sum(map(tuple, rating_groups), ()) + flatten_weights = sum(map(tuple, weights), ()) + length = len(flatten_ratings) + # a vector of all of the skill means + mean_matrix = Matrix([[r.mu] for r in flatten_ratings]) + # a matrix whose diagonal values are the variances (sigma ** 2) of each + # of the players. + def variance_matrix(height, width): + variances = (r.sigma ** 2 for r in flatten_ratings) + for x, variance in enumerate(variances): + yield (x, x), variance + variance_matrix = Matrix(variance_matrix, length, length) + # the player-team assignment and comparison matrix + def rotated_a_matrix(set_height, set_width): + t = 0 + for r, (cur, _next) in enumerate(zip(rating_groups[:-1], + rating_groups[1:])): + for x in range(t, t + len(cur)): + yield (r, x), flatten_weights[x] + t += 1 + x += 1 + for x in range(x, x + len(_next)): + yield (r, x), -flatten_weights[x] + set_height(r + 1) + set_width(x + 1) + rotated_a_matrix = Matrix(rotated_a_matrix) + a_matrix = rotated_a_matrix.transpose() + # match quality further derivation + _ata = (self.beta ** 2) * rotated_a_matrix * a_matrix + _atsa = rotated_a_matrix * variance_matrix * a_matrix + start = mean_matrix.transpose() * a_matrix + middle = _ata + _atsa + end = rotated_a_matrix * mean_matrix + # make result + e_arg = (-0.5 * start * middle.inverse() * end).determinant() + s_arg = _ata.determinant() / middle.determinant() + return math.exp(e_arg) * math.sqrt(s_arg) + + def expose(self, rating): + k = self.mu / self.sigma + return rating.mu - k * rating.sigma + + def make_as_global(self): + return setup(env=self) + + def __repr__(self): + c = type(self) + if callable(self.draw_probability): + f = self.draw_probability + draw_probability = '.'.join([f.__module__, f.__name__]) + else: + draw_probability = '%.1f%%' % (self.draw_probability * 100) + if self.backend is None: + backend = '' + elif isinstance(self.backend, tuple): + backend = ', backend=...' + else: + backend = ', backend=%r' % self.backend + args = ('.'.join([c.__module__, c.__name__]), self.mu, self.sigma, + self.beta, self.tau, draw_probability, backend) + return ('%s(mu=%.3f, sigma=%.3f, beta=%.3f, tau=%.3f, ' + 'draw_probability=%s%s)' % args) + + +def rate_1vs1(rating1, rating2, drawn=False, min_delta=DELTA, env=None): + if env is None: + env = global_env() + ranks = [0, 0 if drawn else 1] + teams = env.rate([(rating1,), (rating2,)], ranks, min_delta=min_delta) + return teams[0][0], teams[1][0] + + +def quality_1vs1(rating1, rating2, env=None): + if env is None: + env = global_env() + return env.quality([(rating1,), (rating2,)]) + + +def global_env(): + try: + global_env.__trueskill__ + except AttributeError: + # setup the default environment + setup() + return global_env.__trueskill__ + + +def setup(mu=MU, sigma=SIGMA, beta=BETA, tau=TAU, + draw_probability=DRAW_PROBABILITY, backend=None, env=None): + if env is None: + env = TrueSkill(mu, sigma, beta, tau, draw_probability, backend) + global_env.__trueskill__ = env + return env + + +def rate(rating_groups, ranks=None, weights=None, min_delta=DELTA): + return global_env().rate(rating_groups, ranks, weights, min_delta) + + +def quality(rating_groups, weights=None): + return global_env().quality(rating_groups, weights) + + +def expose(rating): + return global_env().expose(rating) \ No newline at end of file diff --git a/analysis-master/build/lib/analysis/visualization.py b/analysis-master/build/lib/analysis/visualization.py new file mode 100644 index 00000000..72358662 --- /dev/null +++ b/analysis-master/build/lib/analysis/visualization.py @@ -0,0 +1,34 @@ +# Titan Robotics Team 2022: Visualization Module +# Written by Arthur Lu & Jacob Levine +# Notes: +# this should be imported as a python module using 'import visualization' +# this should be included in the local directory or environment variable +# fancy +# setup: + +__version__ = "1.0.0.000" + +#changelog should be viewed using print(analysis.__changelog__) +__changelog__ = """changelog: + 1.0.0.000: + - created visualization.py + - added graphloss() + - added imports +""" + +__author__ = ( + "Arthur Lu ," + "Jacob Levine ," + ) + +__all__ = [ + 'graphloss', + ] + +import matplotlib.pyplot as plt + +def graphloss(losses): + + x = range(0, len(losses)) + plt.plot(x, losses) + plt.show() \ No newline at end of file diff --git a/analysis-master/dist/analysis-1.0.0.8-py3-none-any.whl b/analysis-master/dist/analysis-1.0.0.8-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..83d5cb968fdb50f333025e6373172553d91df843 GIT binary patch literal 20459 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 Z{&)Yo|K0!Ye;@Gg{{uiF_T2zz0RRUFw=Vzy literal 0 HcmV?d00001 diff --git a/analysis-master/setup.py b/analysis-master/setup.py new file mode 100644 index 00000000..6fb83631 --- /dev/null +++ b/analysis-master/setup.py @@ -0,0 +1,27 @@ +import setuptools + +setuptools.setup( + name="analysis", # Replace with your own username + version="1.0.0.008", + author="The Titan Scouting Team", + author_email="titanscout2022@gmail.com", + description="analysis package developed by Titan Scouting for The Red Alliance", + long_description="", + long_description_content_type="text/markdown", + url="https://github.com/titanscout2022/tr2022-strategy", + packages=setuptools.find_packages(), + install_requires=[ + "numba", + "numpy", + "scipy", + "scikit-learn", + "six", + "matplotlib" + ], + license = "GNU General Public License v3.0", + classifiers=[ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + ], + python_requires='>=3.6', +) \ No newline at end of file diff --git a/data analysis/__pycache__/data.cpython-37.pyc b/data analysis/__pycache__/data.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58b83c4a48a2c2984475e33e57f4ebec6897e590 GIT binary patch literal 4239 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 BYJUI# literal 0 HcmV?d00001 diff --git a/data analysis/__pycache__/superscript.cpython-37.pyc b/data analysis/__pycache__/superscript.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f36ad6398b65079f0f52d839784607fc670fd4e GIT binary patch literal 3965 zcmZ`+%WvGq86Q5EyQ^Kv^1BEe!f@Iy;0TKPaty%?b4dB zQhXwkOcs7Vm2?J-4szkkQS8fP9Q*sR-s^dirf+QKr=g6+%9eMUTZWuNUsA0;Tw_-e zkMko?<^KO69wb>(j6fZSNk&E-<$PtEpS-zEnuk1y^6VfPHnzzpI7KqUQiXO*n&%UJ zKoN&L*-z3$o;27ije{Ue(;)ESM3KamM=D!Y^lGWfSaCK?vdZkWIpd7OWIlNda7FDO zH`P8!j_b84p2%N6YLcsPfZ~%RI!v28M9`fAA(|F4A1__+=lf6NNUm)7!{&;#;ffs) zH(?~FIP8fpVmTF=ANogFBVTpu51Ok~Ca42joAdc7iAH`>_){3aa@>j%J8z0NoY9c3 z1ALmvB(1}y@FyZaN)SgYORnZnZI2=n$)q}e(_3K{rYA*G-1f8Sct6x&a6)GDW7|L5{xf>CN9uJKw;7`$tf1*47hh#6=8Jja{a6_`I z?9?!5Y-)(AcxpT`=f>Ow&aP>WhSp1)yJrS(U1!whmRw-G8e>DIUCVSdbY8lpRT^jJ z+ySjy)7CU>l}>4%nsXPFUQOw1=#fmxPOW(hwDp>{pH0xDkpIBl(yS!jsR6zeQJ}G7%^;_~-3Ky1pWuTjB z?;ry-!&*~rNuot&Ju%I)MG6C-hp`MuvjCNO97^QAz@@|lMTgp;WdoUO4!Qyc zo?45hLZEsDdhsqocoNG*CV8gZ%6!TVCkYIpTohn}Hf^9EL5vL%3qkchF!kB z^Ygn;el2n;FvSxocD8qR-Y=v;9U7iIAyI!3z)y;X7=%?B`Ynh{x9YZ zFm5$39pozInsMlgy<;E80S0oLb5F9eReGmp>6RXj_vkUp7{+$#)Q7c8v$Qbo;NWyv zV|QQ(8~YF1;xd48sNNQ;SC#(d8D$)f)%sB=l5jtb7ie#RP+byRpnD6n4nIUQXs;WH z{5L`?D3X{qkZtq<;dQ`E)*2#WX)ZEb3r(Br-x!oAc88|eTzLF5!kEC^0>-2m&=?Dt zkAcCbp|qtVVZu{`UU=OZ8fUiVa=|ru3s-B&<$>$PTe#Xwt`4~V{uZw8l4}iI|9T5o zuj10>ufMChNZ%w1^oNDII7)E60vZl`-$Z zNvf~aJ=e5zE=FlnNaYCnh=w+ChFqfv6t}3OsA!}UMd%4y@eVqM+R&-n4ZvoLh?5?w z4V1<@V!n$362;V&z=qx7f20j1gMFQy;o^Db))x*E!W2F5t4mc4Mc@ZICv-3m9B~mu zae=xk)Lo_SUFu%bc^$YvqZL{uvttUvEES}>vQ+S|nAB<^KhG>=|BKRwx__EG$A2sx zZs-gz=Pv3%3!EkuvC=)WI72AirO}e)Z~G9A)^` zjW7Q5o3H-%U|@+4F;}MG$~wwZFkAR#Qr0ltQKl>qEel)Y<2s9<2e^3xTxZcJF4SeZ zujX|I~_^FuUKSp5Os(&y9)wwE%bUKiI1YH;U($p7Ab5$S_enUa6D&0cL qyEo4HG>z}mRa4wVXTN7#wqxSPrPp<++IqHk!}Gig-c`@_Zv79Y;1Nv# literal 0 HcmV?d00001 diff --git a/data analysis/config/competition.config b/data analysis/config/competition.config new file mode 100644 index 00000000..511e258a --- /dev/null +++ b/data analysis/config/competition.config @@ -0,0 +1 @@ +2020ilch \ No newline at end of file diff --git a/data analysis/config/database.config b/data analysis/config/database.config new file mode 100644 index 00000000..e69de29b diff --git a/data analysis/config/stats.config b/data analysis/config/stats.config new file mode 100644 index 00000000..5b0501ac --- /dev/null +++ b/data analysis/config/stats.config @@ -0,0 +1,14 @@ +balls-blocked,basic_stats,historical_analysis,regression_linear,regression_logarithmic,regression_exponential,regression_polynomial,regression_sigmoidal +balls-collected,basic_stats,historical_analysis,regression_linear,regression_logarithmic,regression_exponential,regression_polynomial,regression_sigmoidal +balls-lower-teleop,basic_stats,historical_analysis,regression_linear,regression_logarithmic,regression_exponential,regression_polynomial,regression_sigmoidal +balls-lower-auto,basic_stats,historical_analysis,regression_linear,regression_logarithmic,regression_exponential,regression_polynomial,regression_sigmoidal +balls-started,basic_stats,historical_analyss,regression_linear,regression_logarithmic,regression_exponential,regression_polynomial,regression_sigmoidal +balls-upper-teleop,basic_stats,historical_analysis,regression_linear,regression_logarithmic,regression_exponential,regression_polynomial,regression_sigmoidal +balls-upper-auto,basic_stats,historical_analysis,regression_linear,regression_logarithmic,regression_exponential,regression_polynomial,regression_sigmoidal +wheel-mechanism +low-balls +high-balls +wheel-success +strategic-focus +climb-mechanism +attitude \ No newline at end of file diff --git a/data analysis/data.py b/data analysis/data.py new file mode 100644 index 00000000..b7fec8b5 --- /dev/null +++ b/data analysis/data.py @@ -0,0 +1,102 @@ +import requests +import pymongo +import pandas as pd +import time + +def pull_new_tba_matches(apikey, competition, cutoff): + api_key= apikey + x=requests.get("https://www.thebluealliance.com/api/v3/event/"+competition+"/matches/simple", headers={"X-TBA-Auth_Key":api_key}) + out = [] + for i in x.json(): + if (i["actual_time"] != None and i["actual_time"]-cutoff >= 0 and i["comp_level"] == "qm"): + out.append({"match" : i['match_number'], "blue" : list(map(lambda x: int(x[3:]), i['alliances']['blue']['team_keys'])), "red" : list(map(lambda x: int(x[3:]), i['alliances']['red']['team_keys'])), "winner": i["winning_alliance"]}) + return out + +def get_team_match_data(apikey, competition, team_num): + client = pymongo.MongoClient(apikey) + db = client.data_scouting + mdata = db.matchdata + out = {} + for i in mdata.find({"competition" : competition, "team_scouted": team_num}): + out[i['match']] = i['data'] + return pd.DataFrame(out) + +def get_team_pit_data(apikey, competition, team_num): + client = pymongo.MongoClient(apikey) + db = client.data_scouting + mdata = db.pitdata + out = {} + return mdata.find_one({"competition" : competition, "team_scouted": team_num})["data"] + +def get_team_metrics_data(apikey, competition, team_num): + client = pymongo.MongoClient(apikey) + db = client.data_processing + mdata = db.team_metrics + return mdata.find_one({"competition" : competition, "team": team_num}) + +def unkeyify_2l(layered_dict): + out = {} + for i in layered_dict.keys(): + add = [] + sortkey = [] + for j in layered_dict[i].keys(): + add.append([j,layered_dict[i][j]]) + add.sort(key = lambda x: x[0]) + out[i] = list(map(lambda x: x[1], add)) + return out + +def get_match_data_formatted(apikey, competition): + client = pymongo.MongoClient(apikey) + db = client.data_scouting + mdata = db.teamlist + x=mdata.find_one({"competition":competition}) + out = {} + for i in x: + try: + out[int(i)] = unkeyify_2l(get_team_match_data(apikey, competition, int(i)).transpose().to_dict()) + except: + pass + return out + +def get_pit_data_formatted(apikey, competition): + client = pymongo.MongoClient(apikey) + db = client.data_scouting + mdata = db.teamlist + x=mdata.find_one({"competition":competition}) + out = {} + for i in x: + try: + out[int(i)] = get_team_pit_data(apikey, competition, int(i)) + except: + pass + return out + +def push_team_tests_data(apikey, competition, team_num, data, dbname = "data_processing", colname = "team_tests"): + client = pymongo.MongoClient(apikey) + db = client[dbname] + mdata = db[colname] + mdata.replace_one({"competition" : competition, "team": team_num}, {"_id": competition+str(team_num)+"am", "competition" : competition, "team" : team_num, "data" : data}, True) + +def push_team_metrics_data(apikey, competition, team_num, data, dbname = "data_processing", colname = "team_metrics"): + client = pymongo.MongoClient(apikey) + db = client[dbname] + mdata = db[colname] + mdata.replace_one({"competition" : competition, "team": team_num}, {"_id": competition+str(team_num)+"am", "competition" : competition, "team" : team_num, "metrics" : data}, True) + +def push_team_pit_data(apikey, competition, variable, data, dbname = "data_processing", colname = "team_pit"): + client = pymongo.MongoClient(apikey) + db = client[dbname] + mdata = db[colname] + mdata.replace_one({"competition" : competition, "variable": variable}, {"competition" : competition, "variable" : variable, "data" : data}, True) + +def get_analysis_flags(apikey, flag): + client = pymongo.MongoClient(apikey) + db = client.data_processing + mdata = db.flags + return mdata.find_one({flag:{"$exists":True}}) + +def set_analysis_flags(apikey, flag, data): + client = pymongo.MongoClient(apikey) + db = client.data_processing + mdata = db.flags + return mdata.replace_one({flag:{"$exists":True}}, data, True) \ No newline at end of file diff --git a/data analysis/get_team_rankings.py b/data analysis/get_team_rankings.py new file mode 100644 index 00000000..cec2aa08 --- /dev/null +++ b/data analysis/get_team_rankings.py @@ -0,0 +1,59 @@ +import data as d +from analysis import analysis as an +import pymongo +import operator + +def load_config(file): + config_vector = {} + file = an.load_csv(file) + for line in file[1:]: + config_vector[line[0]] = line[1:] + + return (file[0][0], config_vector) + +def get_metrics_processed_formatted(apikey, competition): + client = pymongo.MongoClient(apikey) + db = client.data_scouting + mdata = db.teamlist + x=mdata.find_one({"competition":competition}) + out = {} + for i in x: + try: + out[int(i)] = d.get_team_metrics_data(apikey, competition, int(i)) + except: + pass + return out + +def main(): + + apikey = an.load_csv("keys.txt")[0][0] + tbakey = an.load_csv("keys.txt")[1][0] + + competition, config = load_config("config.csv") + + metrics = get_metrics_processed_formatted(apikey, competition) + + elo = {} + gl2 = {} + + for team in metrics: + + elo[team] = metrics[team]["metrics"]["elo"]["score"] + gl2[team] = metrics[team]["metrics"]["gl2"]["score"] + + elo = {k: v for k, v in sorted(elo.items(), key=lambda item: item[1])} + gl2 = {k: v for k, v in sorted(gl2.items(), key=lambda item: item[1])} + + for team in elo: + + print("teams sorted by elo:") + print("" + str(team) + " | " + str(elo[team])) + + print("*"*25) + + for team in gl2: + + print("teams sorted by glicko2:") + print("" + str(team) + " | " + str(gl2[team])) + +main() \ No newline at end of file diff --git a/data analysis/superscript.py b/data analysis/superscript.py new file mode 100644 index 00000000..d57eab26 --- /dev/null +++ b/data analysis/superscript.py @@ -0,0 +1,374 @@ +# Titan Robotics Team 2022: Superscript Script +# Written by Arthur Lu & Jacob Levine +# Notes: +# setup: + +__version__ = "0.0.5.000" + +# changelog should be viewed using print(analysis.__changelog__) +__changelog__ = """changelog: + 0.0.5.000: + improved user interface + 0.0.4.002: + - removed unessasary code + 0.0.4.001: + - fixed bug where X range for regression was determined before sanitization + - better sanitized data + 0.0.4.000: + - fixed spelling issue in __changelog__ + - addressed nan bug in regression + - fixed errors on line 335 with metrics calling incorrect key "glicko2" + - fixed errors in metrics computing + 0.0.3.000: + - added analysis to pit data + 0.0.2.001: + - minor stability patches + - implemented db syncing for timestamps + - fixed bugs + 0.0.2.000: + - finalized testing and small fixes + 0.0.1.004: + - finished metrics implement, trueskill is bugged + 0.0.1.003: + - working + 0.0.1.002: + - started implement of metrics + 0.0.1.001: + - cleaned up imports + 0.0.1.000: + - tested working, can push to database + 0.0.0.009: + - tested working + - prints out stats for the time being, will push to database later + 0.0.0.008: + - added data import + - removed tba import + - finished main method + 0.0.0.007: + - added load_config + - optimized simpleloop for readibility + - added __all__ entries + - added simplestats engine + - pending testing + 0.0.0.006: + - fixes + 0.0.0.005: + - imported pickle + - created custom database object + 0.0.0.004: + - fixed simpleloop to actually return a vector + 0.0.0.003: + - added metricsloop which is unfinished + 0.0.0.002: + - added simpleloop which is untested until data is provided + 0.0.0.001: + - created script + - added analysis, numba, numpy imports +""" + +__author__ = ( + "Arthur Lu ", + "Jacob Levine ", +) + +__all__ = [ + "main", + "load_config", + "simpleloop", + "simplestats", + "metricsloop" +] + +# imports: + +from analysis import analysis as an +import data as d +import numpy as np +import matplotlib.pyplot as plt +from os import system, name +from pathlib import Path +import time +import warnings + +def main(): + warnings.filterwarnings("ignore") + while(True): + + current_time = time.time() + print("[OK] time: " + str(current_time)) + + start = time.time() + config = load_config(Path("config/stats.config")) + competition = an.load_csv(Path("config/competition.config"))[0][0] + print("[OK] configs loaded") + + apikey = an.load_csv(Path("config/keys.config"))[0][0] + tbakey = an.load_csv(Path("config/keys.config"))[1][0] + print("[OK] loaded keys") + + previous_time = d.get_analysis_flags(apikey, "latest_update") + + if(previous_time == None): + + d.set_analysis_flags(apikey, "latest_update", 0) + previous_time = 0 + + else: + + previous_time = previous_time["latest_update"] + + print("[OK] analysis backtimed to: " + str(previous_time)) + + print("[OK] loading data") + start = time.time() + data = d.get_match_data_formatted(apikey, competition) + pit_data = d.pit = d.get_pit_data_formatted(apikey, competition) + print("[OK] loaded data in " + str(time.time() - start) + " seconds") + + print("[OK] running tests") + start = time.time() + results = simpleloop(data, config) + print("[OK] finished tests in " + str(time.time() - start) + " seconds") + + print("[OK] running metrics") + start = time.time() + metricsloop(tbakey, apikey, competition, previous_time) + print("[OK] finished metrics in " + str(time.time() - start) + " seconds") + + print("[OK] running pit analysis") + start = time.time() + pit = pitloop(pit_data, config) + print("[OK] finished pit analysis in " + str(time.time() - start) + " seconds") + + d.set_analysis_flags(apikey, "latest_update", {"latest_update":current_time}) + + print("[OK] pushing to database") + start = time.time() + push_to_database(apikey, competition, results, pit) + print("[OK] pushed to database in " + str(time.time() - start) + " seconds") + + clear() + +def clear(): + + # for windows + if name == 'nt': + _ = system('cls') + + # for mac and linux(here, os.name is 'posix') + else: + _ = system('clear') + +def load_config(file): + config_vector = {} + file = an.load_csv(file) + for line in file: + config_vector[line[0]] = line[1:] + + return config_vector + +def simpleloop(data, tests): # expects 3D array with [Team][Variable][Match] + + return_vector = {} + for team in data: + variable_vector = {} + for variable in data[team]: + test_vector = {} + variable_data = data[team][variable] + if(variable in tests): + for test in tests[variable]: + test_vector[test] = simplestats(variable_data, test) + else: + pass + variable_vector[variable] = test_vector + return_vector[team] = variable_vector + + return return_vector + +def simplestats(data, test): + + data = np.array(data) + data = data[np.isfinite(data)] + ranges = list(range(len(data))) + + if(test == "basic_stats"): + return an.basic_stats(data) + + if(test == "historical_analysis"): + return an.histo_analysis([ranges, data]) + + if(test == "regression_linear"): + return an.regression(ranges, data, ['lin']) + + if(test == "regression_logarithmic"): + return an.regression(ranges, data, ['log']) + + if(test == "regression_exponential"): + return an.regression(ranges, data, ['exp']) + + if(test == "regression_polynomial"): + return an.regression(ranges, data, ['ply']) + + if(test == "regression_sigmoidal"): + return an.regression(ranges, data, ['sig']) + +def push_to_database(apikey, competition, results, pit): + + for team in results: + + d.push_team_tests_data(apikey, competition, team, results[team]) + + for variable in pit: + + d.push_team_pit_data(apikey, competition, variable, pit[variable]) + +def metricsloop(tbakey, apikey, competition, timestamp): # listener based metrics update + + elo_N = 400 + elo_K = 24 + + matches = d.pull_new_tba_matches(tbakey, competition, timestamp) + + red = {} + blu = {} + + for match in matches: + + red = load_metrics(apikey, competition, match, "red") + blu = load_metrics(apikey, competition, match, "blue") + + elo_red_total = 0 + elo_blu_total = 0 + + gl2_red_score_total = 0 + gl2_blu_score_total = 0 + + gl2_red_rd_total = 0 + gl2_blu_rd_total = 0 + + gl2_red_vol_total = 0 + gl2_blu_vol_total = 0 + + for team in red: + + elo_red_total += red[team]["elo"]["score"] + + gl2_red_score_total += red[team]["gl2"]["score"] + gl2_red_rd_total += red[team]["gl2"]["rd"] + gl2_red_vol_total += red[team]["gl2"]["vol"] + + for team in blu: + + elo_blu_total += blu[team]["elo"]["score"] + + gl2_blu_score_total += blu[team]["gl2"]["score"] + gl2_blu_rd_total += blu[team]["gl2"]["rd"] + gl2_blu_vol_total += blu[team]["gl2"]["vol"] + + red_elo = {"score": elo_red_total / len(red)} + blu_elo = {"score": elo_blu_total / len(blu)} + + red_gl2 = {"score": gl2_red_score_total / len(red), "rd": gl2_red_rd_total / len(red), "vol": gl2_red_vol_total / len(red)} + blu_gl2 = {"score": gl2_blu_score_total / len(blu), "rd": gl2_blu_rd_total / len(blu), "vol": gl2_blu_vol_total / len(blu)} + + + if(match["winner"] == "red"): + + observations = {"red": 1, "blu": 0} + + elif(match["winner"] == "blue"): + + observations = {"red": 0, "blu": 1} + + else: + + observations = {"red": 0.5, "blu": 0.5} + + red_elo_delta = an.elo(red_elo["score"], blu_elo["score"], observations["red"], elo_N, elo_K) - red_elo["score"] + blu_elo_delta = an.elo(blu_elo["score"], red_elo["score"], observations["blu"], elo_N, elo_K) - blu_elo["score"] + + new_red_gl2_score, new_red_gl2_rd, new_red_gl2_vol = an.glicko2(red_gl2["score"], red_gl2["rd"], red_gl2["vol"], [blu_gl2["score"]], [blu_gl2["rd"]], [observations["red"], observations["blu"]]) + new_blu_gl2_score, new_blu_gl2_rd, new_blu_gl2_vol = an.glicko2(blu_gl2["score"], blu_gl2["rd"], blu_gl2["vol"], [red_gl2["score"]], [red_gl2["rd"]], [observations["blu"], observations["red"]]) + + red_gl2_delta = {"score": new_red_gl2_score - red_gl2["score"], "rd": new_red_gl2_rd - red_gl2["rd"], "vol": new_red_gl2_vol - red_gl2["vol"]} + blu_gl2_delta = {"score": new_blu_gl2_score - blu_gl2["score"], "rd": new_blu_gl2_rd - blu_gl2["rd"], "vol": new_blu_gl2_vol - blu_gl2["vol"]} + + for team in red: + + red[team]["elo"]["score"] = red[team]["elo"]["score"] + red_elo_delta + + red[team]["gl2"]["score"] = red[team]["gl2"]["score"] + red_gl2_delta["score"] + red[team]["gl2"]["rd"] = red[team]["gl2"]["rd"] + red_gl2_delta["rd"] + red[team]["gl2"]["vol"] = red[team]["gl2"]["vol"] + red_gl2_delta["vol"] + + for team in blu: + + blu[team]["elo"]["score"] = blu[team]["elo"]["score"] + blu_elo_delta + + blu[team]["gl2"]["score"] = blu[team]["gl2"]["score"] + blu_gl2_delta["score"] + blu[team]["gl2"]["rd"] = blu[team]["gl2"]["rd"] + blu_gl2_delta["rd"] + blu[team]["gl2"]["vol"] = blu[team]["gl2"]["vol"] + blu_gl2_delta["vol"] + + temp_vector = {} + temp_vector.update(red) + temp_vector.update(blu) + + for team in temp_vector: + + d.push_team_metrics_data(apikey, competition, team, temp_vector[team]) + +def load_metrics(apikey, competition, match, group_name): + + group = {} + + for team in match[group_name]: + + db_data = d.get_team_metrics_data(apikey, competition, team) + + if d.get_team_metrics_data(apikey, competition, team) == None: + + elo = {"score": 1500} + gl2 = {"score": 1500, "rd": 250, "vol": 0.06} + ts = {"mu": 25, "sigma": 25/3} + + #d.push_team_metrics_data(apikey, competition, team, {"elo":elo, "gl2":gl2,"trueskill":ts}) + + group[team] = {"elo": elo, "gl2": gl2, "ts": ts} + + else: + + metrics = db_data["metrics"] + + elo = metrics["elo"] + gl2 = metrics["gl2"] + ts = metrics["ts"] + + group[team] = {"elo": elo, "gl2": gl2, "ts": ts} + + return group + +def pitloop(pit, tests): + + return_vector = {} + for team in pit: + for variable in pit[team]: + if(variable in tests): + if(not variable in return_vector): + return_vector[variable] = [] + return_vector[variable].append(pit[team][variable]) + + return return_vector + +main() + +""" +Metrics Defaults: + +elo starting score = 1500 +elo N = 400 +elo K = 24 + +gl2 starting score = 1500 +gl2 starting rd = 350 +gl2 starting vol = 0.06 +""" \ No newline at end of file diff --git a/data analysis/visualize_pit.py b/data analysis/visualize_pit.py new file mode 100644 index 00000000..afd10e20 --- /dev/null +++ b/data analysis/visualize_pit.py @@ -0,0 +1,59 @@ +# To add a new cell, type '# %%' +# To add a new markdown cell, type '# %% [markdown]' +# %% +import matplotlib.pyplot as plt +import data as d +import pymongo + + +# %% +def get_pit_variable_data(apikey, competition): + client = pymongo.MongoClient(apikey) + db = client.data_processing + mdata = db.team_pit + out = {} + return mdata.find() + + +# %% +def get_pit_variable_formatted(apikey, competition): + temp = get_pit_variable_data(apikey, competition) + out = {} + for i in temp: + out[i["variable"]] = i["data"] + return out + + +# %% +pit = get_pit_variable_formatted("mongodb+srv://api-user-new:titanscout2022@2022-scouting-4vfuu.mongodb.net/test?authSource=admin&replicaSet=2022-scouting-shard-0&readPreference=primary&appname=MongoDB%20Compass&ssl=true", "2020ilch") + + +# %% +import matplotlib.pyplot as plt +import numpy as np + + +# %% +fig, ax = plt.subplots(1, len(pit), sharey=True, figsize=(80,15)) + +i = 0 + +for variable in pit: + + ax[i].hist(pit[variable]) + ax[i].invert_xaxis() + + ax[i].set_xlabel('') + ax[i].set_ylabel('Frequency') + ax[i].set_title(variable) + + plt.yticks(np.arange(len(pit[variable]))) + + i+=1 + +plt.show() + + +# %% + +