From f6c822ca8fb121b41dded78ac766e8e29727be9c Mon Sep 17 00:00:00 2001 From: Alejandro Moreo Date: Fri, 3 Jul 2026 17:07:02 +0200 Subject: [PATCH] improving docs and new example for image datasets --- README.md | 2 +- docs/source/_static/custom.css | 53 ++++++++++++ docs/source/_static/quapy_logo.png | Bin 0 -> 18016 bytes docs/source/_static/quapy_logo_dark.png | Bin 0 -> 17920 bytes examples/19.visualizing_simplex.py | 68 +++++++++++++++ examples/20.cifar10_quantification.py | 64 ++++++++++++++ quapy/data/datasets.py | 2 - quapy/method/_helper.py | 107 ++++++++++++++++++++++++ quapy/tests/test_functional.py | 27 ++++++ quapy/tests/test_plot.py | 41 +++++++++ 10 files changed, 361 insertions(+), 3 deletions(-) create mode 100644 docs/source/_static/custom.css create mode 100644 docs/source/_static/quapy_logo.png create mode 100644 docs/source/_static/quapy_logo_dark.png create mode 100644 examples/19.visualizing_simplex.py create mode 100644 examples/20.cifar10_quantification.py create mode 100644 quapy/method/_helper.py create mode 100644 quapy/tests/test_functional.py create mode 100644 quapy/tests/test_plot.py diff --git a/README.md b/README.md index 730a433..fcb820d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ for facilitating the analysis and interpretation of the experimental results. ### Last updates: -* Version 0.2.0 is released! major changes can be consulted [here](CHANGE_LOG.txt). +* Version 0.2.1 is released! major changes can be consulted [here](CHANGE_LOG.txt). * The developer API documentation is available [here](https://hlt-isti.github.io/QuaPy/index.html) * Manuals are available [here](https://hlt-isti.github.io/QuaPy/manuals.html) diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css new file mode 100644 index 0000000..600ce46 --- /dev/null +++ b/docs/source/_static/custom.css @@ -0,0 +1,53 @@ +.navbar-brand.logo .title.logo__title { + display: none; +} + +.navbar-brand img { + max-height: 2.2rem; + width: auto; +} + +.navbar-brand.logo .title.logo__title { + font-size: 1.05rem; + line-height: 1.15; + white-space: nowrap; +} + +@media (max-width: 1200px) { + .bd-header .navbar-header-items { + min-width: 0; + } + + .bd-header .navbar-header-items__center { + overflow-x: auto; + } + + .bd-header .bd-navbar-elements { + flex-wrap: nowrap; + } +} + +.hero-copy { + font-size: 1.15rem; + line-height: 1.7; + max-width: 56rem; + margin: 0 0 1.5rem 0; +} + +.landing-grid { + margin: 1.2rem 0 2rem 0; +} + +.landing-card { + border-radius: 1rem; + border: 1px solid var(--pst-color-border, #d0d7de); + box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08); +} + +.landing-card .sd-card-title { + font-size: 1.1rem; +} + +[data-theme="dark"] .landing-card { + box-shadow: 0 10px 24px rgba(0, 0, 0, 0.22); +} diff --git a/docs/source/_static/quapy_logo.png b/docs/source/_static/quapy_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..379899384b6284b071ce071d8b5f41738e8a3c0a GIT binary patch literal 18016 zcmXtg1yoy2*L6a0cP-Z9P~6?6P@p&zcX!udrBK|pXmPjV1b3&nySos^sR97N_(GriAi_iM&ph-7pbxMv;&N(;(BX?{ z`W^Z=lB2A)3jo0U;olAD`Aftd`XZ65l$NWigSo4Rk+T`V!^4Bw%HGDs#K_T%*}>T& z<4lkk0H6TKNs6g?W}aqw`Ks;SzKQHNbEJh(+veq+P|(rkVa031I1!ZnDXr0{K-Q{e zP`{&kuo*bl#8Jlg_z}9Hu`GW2yMei>p@fP9NK&C;v#JBzixx*6GU}Xt{q!M+N&Afd zo~HS{gCSjT!V6v7d|YUPFI(c`|Au99F=8kV3E|*O(`J|F=f1JVmf1i$Iyxc+?n?*bhm5-k$_zKn=#ue3IgVBoQ9)cumm&LN*o5$bi z*D}8SI!lrgAa$Pn-*Iuf42K*My%9u3x04NeZ5U9^ehPx~@+>DL87|qtYw6sWA_P_X zo`F9(+EBC6udef(LGWDqz`PpC6@@@*EJQy)D`EjhNQhnrPt_CqwZ8X%QO65YGJ)B_Teu^#H{V2+6#Gwg(sIUF+CI0 zDN2n8JJ@7dXC#sd$?J>1X~*16dDwr05i#^re_#%uPcKC8cwWY1@le&mP-*_g4 z@>A5~sOB8bmlzQ~zkLQ<6YI4*m{$sZY8mqMJ& zJNX~?tkKScJMrkgy-I}Ec5EWn5zaM zxiQxdz{fA#=VZOQavzl@+k$3r_!}b`zb9t?GRq~nXb|@|^-o2T_{h4e`n-kU00Zk)&;52vWX1<$tpOf|QuwOPnVSJXGs7AaP<%#u1f zojChNk9_~%HxF`lB(kH3HqPBLJ?N9H9@fv!u3QR(Q(SHNSg_LqoWK5P16L8{O%-31 z`2yqm49E*rC34|{nHmoN3uU8tLq~ITQIV;d-P5I`P<8jmnMKMyGw->w@|DRsVwMe? zZz=kpc0j%bq>a=i2@yXueg1}#{TpXuNV=#wMRU8T9qZB#{6aMg-6>)>KkrDNd!P|k zwy2%0UBpkkdC_);{SLV73do;G{^<4yFy_wMm_nyxh;X;Dl!dLWZ9?GKF9@#<{p6$8 z89x|*NLre1899(*bvFBNZdeiY%1oS0O5^4i_974ftdJN`@nlnnrU}ENAE4~3&IX)s zSgbjJFxb4%QtNw;*^mzW8m`t|^Z-mk z0*J8!fOMtEc(C7XVS9eb!Em#0cEUb~rM;qT-RHm?UwXQ>)&&S)V8ld=^9r654o#|> zyhhUY8H-p^`k$)qW=;B%jLdhVJk)>D(wbxW^=X;wIyEjy;VcVrgRgbsYVaK#9Ca|I zCH8j-SmYvV3?NrjnYju5HWR!wm@VjD8wkQgLjNYe{y|wGsO$x1qUx(=uFdzxFqu}9 z-4z$nE2E-SUcxn?G7N+a>=8^4+_E!VLJW)nKC4B6GH=ltxN`8`o$IvB;2=W~gH#bM!3YD8 z>4%d5XVzy4`jFo-m)rZOKoiG)bkn?oVH|*9Rt<*`(|ZQ-+;#%?@UK7x*t%0JeG)oA zw;_5Fkq2ClS-|@}vl=a_6{Ai;LBaF`jBY%D9uQER`9dvfBc>I4jQ@sa`t^@z25c}& zKd8mNQKR#s`vqzF-4*Y5N%6|@Vf=fsd|h;`N+}dz_WZ9_jLmDSsGN_%;O%y#cc)=; z3FfdtakVx zNbb3YLicZqVf7Af(aYt(jwC`|{4u7%KP zt1R!Zx*QK@6mA%RcG2Zl7?_UyurES{%zhLsEK7Y>0sq!3xCM}kKq(BJFDv(-1>()h z6W%m;>0IwNPE+vDfjR9o8Ons{6UY7sVFG@T6!SlqhCkr4F~bvYoR=_SXprAiTbTW- zeFbh{7x~oyfg6S8C%qyl$nrgk0{`aeeLH|jX1Cm6`;t%Zloy=FWv~w;Yw-jMsOiDL zK*PL&UDh6TO+At=LVvir9*M@@3E~c*`NToMdn#>VFRlkHkaxP z9%{<72*px~0fM053YuOxXVpN}9%{|BH7hSiSUT}UeQif6Lrc#ma>06u__-tP2`co~ zxzpKIA12;|WPJH#UawI;7yadwmfJzrI9a(W($)hE3OyF^DJi+=8xAV1k{q{mRb|-^ zZr;<_urQgr$Zs(=)y{KN3VMH&@!-Rq#|^8ar_n+4%}Fjwa62Db?)`kIc=80Yqek?E z&7F7K;wb)D-d`c4*6s{9;`bTvI>7b($_5@34KZx`g!vcG!23QvaySF;UB6<-#kUUx zFo4|@pQac8A)j(_!SllGu+eUj3kzD~$H!}COS#-tC`vFQd=XU(#j6{K+{&+W+9)Cm zhq)HNu}#UhV5IrpXJ~P?GJ?0=&fx&>4SzRbLq177alrj@fTx?7)ZbIr(S-3t0ym}& zM80+z9$sF0_I2I|6wm1IBF1q<59$R30ENe96h@dH-ZY zssW^eg1U-Ih~8w3<{0cY-wC~s=6_=x+)u6FlEc7XgIexMO@ke{8`kYL#*fh_x!^s{ z#VKw>_WpQhnBxi`_?(o2X;pW2KVQpZn`GT>vl8kNPy5ISKo^&iFf$IU(`(vh8 zR3)+VThH$(*Ci}yKF8-Hh6!Mw8N`C(fIzZaptS8+I#+3K&vbJcz?P@}cX*8!-*UTm z0~IZ3kgGn$3a+vvAZ2dXDp&289fQA^Cr1_Dwab0_dT?`>lxm_*E=3l5^d}wGMN!C|B-Yi6mq`>SyTT6g)tu>f=}b z^(<{+c%W~7*D5x$P=Q6yan))w5yV1g9>-A`GEZNH5jtGN*8EFv+Ys@9Bz%-yORwEB zVWz`XtzW6h?t9`9qv0}#4+-#D12FOQ5;$L0nis`?H%InB?&WpuV`}lE`cqRXVG3kT z)UDbesR|cAS$eLb>|wayMNp7^2wg$jxFYqk)RH^#uP)Ue-3Lhw*&~b#S+F7uFU$ge z_KSCU`ZX#I%WK_Q6vKz&ZRD-ngw>(WiQF=K-G^UP1PiR2opd{2Uy zsB7OZvYl2}$w?OOJ;3#cwCvLRyJP1y4uW6zQ6BnZvN=M+qaBxG=49)5(ZB>3i=z7a z^*{1xUJ)V8HK(}20ko-P5q%8SK$~xQf|4f=bF zK%N7Cahj+I>Dm6Ooo#N`WFM!reWRBjhrKofFd^pLiE-j6Su0`U;wGZPJJy>&p}NdB zKc&^4H~L?z(+4beAN_%W}RN%kXY8PnFPsL|U9=Hj3YnnF3%%$Oh!TBbDw*nx*S1PR%$^lGeWZR0|NslJmd{@hNX)|41)3uq8G zc#i-OoDz``KUs`D!Ug#l$wTY8M-dVo6eSx@PklRMg_cxH4QOd816pi#JY-pX#4)4q zqVPB>K03OiV>6J!7;d@&I$7ZGdX4cqYsU-uj*iIW`fM2Xf-d}06U2BE*AzNhKi2PW zPBlLc>3xk&;wJ?&r$ymYzRIZ%ST0vbV6`a7x7m%Bm@g#!MFX=aXwgAR7q`ou2Ur&u z=V1uGOqN2@EDW42TaApY5NOn%FBr z04QgxRlgLkjyte=uGC3bQ5ln=6~7nfBj@9Nfe2SgrS%Xuik*&hTg%pBP>>ha6+KH} zg|%WXd6+nLOrJ5z=RXut8P$fOqNPi_6zE`W?z!2OhCic^8p>+8r^$*u#N(c4s~Im| zGy@A!WTaz9a{^v%dB(Un_XsWln75$G7%;QKAk4PFXP#~{V=ADn#f1gH&g_C#;aM@7 zWuMgHXQ`B~-y3szsuxzscc8R@Q0FeM+BiB$ehHldE_&VtA@*(tf##V%HG4H%boXQz z;BUVGzfD6pQK=1@LWB1x1-Go+JVwUUhu#AK)@lhK0BuuNFv9pHg436H&LCLgL`__w zyh;XZ1H&3`b9DD9uGrc~Y52TAK%voYd4M6UNVqn0wv{Jhmtj2*ol{CE%^UUw0&|3> z`s`QB8E0#obMLC0oPto-h1H7Sa)V@ZNHeP$j`O zQlrerAYja)0yiRtOfr4H=OJnY&(n8YP`B=3q%ma)8Zs8aPI)zBHwX$H7Wvu)-ybCO zjM;TpV)C3|3>d5ouKP>B`WW+!0pq?^suTavWg{8cCy_T12(@Ga zPR?V04MFp1DeN#%$8&S5{++$F6iQ;IBa{UmOC~fG&XuX^KW3`gH6rTT7|k>^I_!g< zL_YLpfqm4OX2l261QNp41@>;DnqAz&{;k*=m@{lTiGkakmcP0x)b>-{H$0mD4PamH zLI$kQ)JMoEabKEHSXUf8asB9owTwbS0T!_RVGp?JrYX=$jrvsc4z^~N;6f}CmR}An z8*{~9Kwmq$W;gU^h^RTX<127_twQPmA>c25xqT^uqrks4HRQeZXB{PvSI~R7li#yu zVITW@>tRPZC})|DYDjFJem>^o4yT4m&&NneuLp{2N5g&#E6hDw3dpXYMA`U(=*iF6 zI`ITOr5m{fU8<}pec8+R02*?tM^CkJ%iGPyL#{6|mI8;!Os)%h=Jq0QxL*+T!!%a; z2_cX09-ZMO#FZ;tIdl<~=RaUQqQO8Qt*9-?bFSy;p9>N+bqoi30RLuP zxnQT?u;4_`*qbPF1mNnlRON>Xyci*EZE4rUWX{;B=Qxcxly3}t@Sv%{q?`QKq5o;# zCw#3;Voag=35|6=wVD+W>>jYr9#H?@kHgSc=R2OZE&$Tvwwn!HsBjZvYy)bH1-+r1Z*h0tpg+x@RnR8T9_w9s(2L-GC_4^;KO4o{|@wzb~a<5g>X( zuuVpPfoX{n8sRNDS?jhRRkQg zLB&?UuFQ@UCae_xUZHw(Z%XC$FGE7Af6xI zU2_o;VJh?OqsDd-orL^pa!=b0#s`&&^TE%T`8N(qfLd(13jZ8J`DkrD$ zVQn$M9CrW!+>0p4J>#`|4Kj=u;kFB@Hk*$IHy0_TrmsH;z96+T=2D8u&$O(yiCDH9+%Rk)!yfYaja zOY0>gc9laW2`)-vNqKsHA6{>M=&C%_uM6QMv0ZEOQPc!8*Dhq8D<5JrxhXYMV!Q7avaEqo6}?|g@2e(d@`b3mHVO*!)Kv5-k5`^GvI^syJ>+5^l;yHdo-KkIf`WEd*>^j_t#vdZoGPn%r|yU_ei3&f z3I$9QlR0f|i+?kCytjP}7mBh5W=VxYxu=veppDaGgad4RyXaGBlGf$;Fpmyf-CK?Z zPt$ES|452v`~_arZ3zQ~ujZKSjZ{%!Q%9CYLC5m8D??868w&@!nriYh3WSk{3AHCj zZ(nhC~$Hzq>(YGXKShrDB11gnil*lgV`5RtMXrXlDCzwa0hdFR?(~L>r)Mz~l!1{Mvi7@T$89MqS zF#N8;q;dJJ5`tf4_#+~3(a`c|qNFznnL{=qK|1v$&Qe>51A$fWj|pRoTfovM5A+bZ zDS}uIe)DfE^dILcT(FpWQ@6JMEM;^14ohuB4xZD=$~{#^HFR@M)uS1OlO>OYe`3ve z^7c>d>Bv{E40^jpb~>@dEe;_HhArekv9<39CCuCyJ| z2TEOg18wE#i^J#Fi#SqyNfl0FJeH1B!k)yyX%MZU>tfsEDHdSO#^p33ttSwbyAqL~ zs56}dRz&-GJu=IS*Ss#xpm!c_PJ1hSgc4tgt%iP)XowEhuzd$S@LNrjWeGPTQMfl! zo4?TxW=4m`Kh89V0g6s}oZWc^t6HhnLk)i-Z=inQP7fPL3EPjUEYZHHbmJC)5u7au z#FwEfls%-^dU|?~)0lQ6B>*c;ErfXS^6jJSGcH7daTNyjhX8F(X(P9uy1JAHw6{lZ znVB>uT7{mn>2ulFi%4@eDb*!OZ%3p+8Ht}xED@A*(MKe74S;5IJV4K9BFgeQU#%wj z@V?>o_pv1eZtMmuhq-~S*t->a^QZY_qe;Ea#%+bhj)EG(?Tv+77}CK)fZ~#78;`b@ z=MpIYlE#E-wDB>&5j{{1`?r>98Ayz#HMO}f8!N8RR~?CZebT(jO1O(Cn3`P_qtB4A7+Nb=vACOh+`UcK*jyOIc{p{>2TKF^`7N zK=qG=5Js!lRip$=L5i}Y-=R{BBw~F_dyGCsGl$r6U-QZfURoxhkuY?a_v?N1{cB?$ zEe;RgXM$4HZ~Q|)7;QDF;h4c%CCVTMbdiI1_x-X5+8op>Mp+s!$(;IKAxC7_J)5_d zfvGY-pZzcD%a^2bWuQlvkq)tA#H8-{_wYh`q(%1)SKYrd{;?t z3yknwicl@Q%6V`}%a;}++dyZC2}cZE9b49yZkr~1xhyMkPRJxqqRRmCLoEr(Kv1*l zpd?~R<~ut%pM$Bid`d4zb0Plkxr(NbTF5g%@{&M|ii=;R4e0PulO=#JIpLig9>Bm70i=w;M~n$X|DiwfL@NciJC-pyfUpT8=9?QkYgIT*PB zrp;4k`A$f)j#?^3cbD?*k-t}KK_a%))9Gi%yx$G8BC^!oUqrI+)Mekw;}7i?z2JqUpA0!fqtzVPHHb65a7 zU0-*`h1|V`LRJ;R^sUelFHwgfD;vzlnc`EN+~B@In|=*_oq6NTh@hlIwY7cWaqDP= zyhD9=_+4)_X~$c)X?qInBJI(s6DD-`acwJX8%5)fb90-5$Nb1@7Q$9KqqJr?DV#>^ z@>_M3gjHy1a=J*)0zw;Ng=J}?psx$3Iwp4GSKi`Qy?MF{qY1-WNI$X#l+3s4u{ai1 z(#$fG8NP6PHjk1pq7Q=&4nvJjKJj&T5K%FoC4k956LiYmFY6n*( zU|*P3aMN#8>~PX$oi#jBkY#ZZK^6;7F&omg-fh zAYcD$GxWa)zC<0e1qB2i@#axImjU<2+S=^66g+O9$zsAj3tAn9P>z}*e+f+v75~ja6f>qS?UL7=4FPh`?0c8+(OO!A#+Y3@@3;z8{a|UgVDk zX(~tB_AZY&O2`F*W%j>88-xqFTS&TJFB@vwr4K_iTU53Z$ETsF1)Bf!@(zT7r#sE# z-ZQAc-Ao12KGu@X!EUv}GEx(UjfCme>C;if*fhRkig-}@)i1po<2icF;W|r`*9z_f zF5S+Pd~W>17Hg{NsmGeV$Hf;Ufcah;Ik5Q{d9}5GletgaTDm|B{o5^Pa>246L{qm- zC(cysUCm3+K|B-+4-9(^fGz81&fdXa6F!$JOhD{=Fb`wn9Ogv#eJufoM{Z!mEb-!{ ze6RS|8xr$7jxW$|mBv^zX3#54wN$+H)n=nc>^P&2c|v-jxm*cGmqHQi)P!q=0pB?x zAcauy6i-3_PAS+Ys%KI)WpUvQ)S)EuGJs^5O2WH39@pz-cxx+*audcBhWIJ}@h|R2 z*0k36-DsiQUw8^;2dPuqRoy}W)U(!0Mu_L6l7fuZL3FEEc|8Wh{xkMy0LofVa@tyKb$dsLV*<>-^oDfEB_d8-J%@w^jO zt;upiS8)Cy{>EYZ!c&GXq|N5XH(U!q8 z39Z;6Nbr-^w%n6ZHr2Nh9^G#jFdd5M66tgrMbTaos(dbV8sljZNiOGJzYgh^1wUsH$3k2hb#BGmP=FiK1B zt20g!^=7nT$7>N*=ihLZo(rQ7m%sdQLw)4nghEQ`%8Q#}9{{OyEYyX}{Q*|&$(yPd z2ev;PLYio)9@DKai&+RIC_Je|X#=M)OpoP<^oyPOWw(!$Vn}Xv+v;4!AP6~!Es6C8 zV`P!!@s$IwNL(MqOx4Q0c;Y)`(9XEfj*%X~lC5YD5m3YxHPlY4lSbqaBaA&UKnno23lLIYFEeDSl~6)(sViI08{SRmdXWuFq1tXR z>T;Rjvo_PBD;pQf|6W*Z$9AUQpL;lOh$9~MjiGJ0f@5P<|&o&G~CH6{L4gA|A@-awA9FhgaM;PABbPd z^giuS^@V*tH}C(V?gfKRro5hO)J<2%J?+!{kq;4rB#qNb+HilIc|Wi>hLD#)R0#&K zRd^4RFm_C4AM&$zmb%4Mv_K69lRxN7kUyz_z<22xExsp8Oa|0bi<_RGf8_zsjiD1H zNybK^PNEUJ6d0Tq52;P7a~D`;(>c7x14d7*XH|bYpS~qXLOKbAGH*)MQ@sr#=q5@k z#1!wZ1H*D+;VSDtSO&>~4CemuJ%lWW6Y&^zfeo8(0-=?=-#FFlAOyomZ@(zaX45Ul zUYZ!yHA!D~7C#Zvikz&Z|4c9*XXJ&H!?z*K|cHa*PVGIHZ$L!^iU@g4tkJ3yWj zH(w+-&5wEfIu5~N?PBz!kpUcNUkLopZzEZj%pfu<(H=_(MT^3zF`wM{eBI0A3w5ba zd^2<_{m07c^Ay}{APhA8nsnbor4i~f{VDA>#VyjEBCu?;qbE+W53X< zxFgFrnlqCE>A}juf?@ z*s%sz4(&9PRT}bqFESpEFL~MvsBDEY>NK=EJEDahBL{3Gc5@4X?NLgp7d`K_ z42+A~%^tcpy;il;&lzmbZOIwJ8fqjT?qg8`v@s~CCu)0a-!tCwq^gBe^xqG}u$)e6 zC$8v6rNA86O^*hc)Ch>wbZ6he%CU2$Ns@o5fmZ((fDSqr3-6|o6tN>h1$`Z>G^u_9 zehrV2ksmGcxIS0Y`v5|RL_@C+8U&2RnLZ3)7!zqrID79wry8$m(%6x^q)quA4`M7v z#ivawAMc?vA0Ow8hz)HtUTkdp*Vmts1N&P3x4N1QrX+~xshl%XmlEq6r|$48QJ1v4QfvTNs;%?Y3Q!O(@+Cw6Z8QAMqC7{g+xUsfW+qD?CsYa%AY zwQq#N%b&E?lPVLh81Lu2NX75%?IfuIu^ z#-!9QjBT4^`m&S`NdnkrpCKWg>OF4{&Cp0R`P%&3I@Lj=vzR)_9B5OQ=tXn$9*?zp zu)b54YUO@b=MUq;(x+wHyR7(2RaSB;6lH-v)UA+d@US@UPJtrA1p;H#+#K@?VPs-& z*PdizIIDwi!A&^YA3Soc-He$KaWhReUgJ4*9Dh~K`|qzCQPl&Mq13^8SQo%M1mCWW zpi404`bWhZvea{R)rXgFXm|!+H+1Tt%AK*8iYv?L3cxG;Uc<$YBB!w}8nI3KJ8}ha z|6-dPl6)yHM2on4m|;|{d(nbW569K5P9tY{C0n#^Cw>hF-}_a|e`mqcp7;)=TnSAX za;1zgw=%M;&Yaq4TL-2DhjU;IL&=MWOoC3osk&jNZ6ugc#j!z^)N|m4P%0V0k>Eel zw0Z-X@s*|^!hmFHkX7~M1OqBl+rB(-$Kh6B^Y6Bk$SLhFcy~jw5b-++0j8roOI}4FC1?snyE}9?FRw}Hz&Z=$Zo1i(*y?24OmlSnyw-zC2mk(Jak#>ipe~W_gRPHtQ9e*LS;1oo$s+ty&dmn^tu^!_1v?CHb>=wVeFZ2s zzA*K(unAeW%>7{~z#TaBxhS{?Ht95i05~`b^CS%(9q;JFKJ|GzGOM}X^0fT@B#6-N zi&Md!ZoBgYIH`uHHO4*{{nPa|LNxl3!U!_^dpqm9@j(uMG}a%>r(Pr%jE{Wt-g7*S zVIr?`M*^0Ms*9PF*v!D5ujyPi_3O)Ct_+^*8J*J6qazvT$xDkpMlB+sq5`9-<05s| zno7~k@Ky4Q-F+5bBz^aZ&SOF=Zw4tto@E0MQ)DlA4Q+v*#!CAsMRP4TJb{JmE1|Dy zxMMj7m0s`T;&ZXD_);^|TFJYbKzn=I>6vb$76R1pBL!=bsBPxhmgV862Dj(;HTfLE zLMu@V9_>YV0R&PBxTzmb83}{q7o&3 zf!E7Pb5#i5^gRwzxqe7$77P3+=N_@%um@!87^4izQ{hbD<4O7G7){t;(XO~>4&SgF zPGP!kX))omE`PCO-;nrSp1IRUV|P%PlVi#Xj?_h9ues!xmyVKHY#;xaJNc8suQOQO z?b5Xj$`=9-H1Ei|?-y;>0g|)_TJ1KgEmso&0t8>1`7@EaNc_9>1OmK#W6XriGut@xK3@0@h{iej}T$fo*MN9j8h}AASZ^|tO zXHKsAu(bqXAvD?){vg%l{*xNJ$h^E_4DjApfbhoC>G1z(qKmM9yHN-KTKdVFe@H}T zIsDAE>S=H@iZ`Q$mL0c#yCWKeMa$B;{(`=tt6OE{7`=jmkUs4_WtpM6c~!WNam`gg zDp#$Nidg~?32f#&vX2 z4N-FY^tvCWaIFCkETkGhSc#>xro^5mh{aDPjHK53wysBX!NpB;1w7oN0WHh9Ng2*h zO_!dG{)Rm|B&;Ue9Yf9ELM?CXP*ha+<>@jFJD_}r60RA%IlX)hCH?RdGjfaRmN*C* z{E!tGjlPVn33J0g2QGiC>#v_{L()&rHZXr$D01`^1L7!*o^ggCef}ie&(E_&(^+kc z%2qkPlSa0;K%1&xWHLf;?-gZ(l`L;p{g+zu%$npRJ;d!ZvQf1UsDsU9&y`xdXnytS z+0!lLCT>^hmR1ixpoc-_v6cIVst*#k>()$fIJn2r6w6XEj^s2KRG?NTf~S@IbrR(#!( zw<`krV>8cQ!xi2+E@9AGopk9P#q0#SFHS7=yo9)3=2mRuocY@(1N(=Xg1^Q_KS_ta1Z_yRc~9GIA%Mf0S1fASY#MVKVzAiDG~p zce+$-J$pv|@!KqK4lI+^UpYac@52$k>v5>hO3<=Xy9)Iljxq3c)2{ksq|Q7(^!Trs~Q8s0j}yth#r^H7%TR z4||V(ZlBT#h4|BxGBN~7#n*Z_PzJ5SvARe`6;7x&cFx+dJwQ!(_!Hpq2^N7*bZtS4 zui+oa?rQw~9tog4wKO1@%g(T=)d>X1o8r_M^CyfH_`Hlg7?M+Gj~zZ+GC4Ik3% z!v$SCqeViET|m)`Bd*v@-(~4s98clUq8#Y>sa9K$(dgGugdF&aZ-Cj%1?v^g5st%AzBlV z0N4>b$p`*l48*lHWP_r{*c!}q_q>NZ86`%8QSum?0wLt)15Qh zk1o$x^W(AZy7f{E5g!^!dk_ZrjV)h0zLS1e@NwhAM!`Q#P|bpIt5oz@?y!OQBn)_T z_O74RYv?%n(Uel*Xt7}1?Gq(|`NP#!o}MhXdCpnysxOx=x`Nwg_x&C$+U>V?f zto(2po!;%`=Jw+xyM45tu-6@x>Kv)x?4N-~0R~m{%O5`}ChX@^&G~5yBzaFL`ecWy zjM2bE<|nKVe^fcSCUmwaR20#`|Bt_g#pGLvQ@90NyH z5Bv1Wq8kdsGO>y!^ZkOaALvjTZU1$-(`CF2>ZJe;6hQ%;DcA;gdIk4FE!xA z^O}LGk`a42;_v8y;oUN%9i@wo|xeCeE zTr0fK=o4Zzyl*_rzKH>ww}sSg!qqO)VM5m32Ol0ZWwqZ&=^=Oj%PKX zVjT2b2bdL-3-$4L$Q&lG3+-dXxyp+}VE0qqWY+~Z<_%@2@X^2>H(|5hCd_CbaMt=n z$Jc8ma`!>ab!?IPufVAcV`g;H@5uV6SWn+65lRvih%XDj{+uz-cl)RThH?LY_6mX1 zQU8Bhzag~^X8;#eTa7G-sRwmXr49ii6T%_40R7>Ov=)lV2(9w&bQ+u(RGcJ*uzBwW zab7}Q!=l6_CoM^%HkMpu)szmiT%P3akdfPR0_9b#3%Fnomz+WQ&7f~sv{0zEagDRW zqu$#|AY;yzlQQGaiGj7z3Xid7>67(Eu*23tu<=3zxGM>sTkz9|k2MsGyDO6EEgLxx zMY)xotBN^0O8Xi>gR?>?cmjVZb$N6(z~Sepr&n(@9}Nkmcov?q_zj0e7X8Qcv&Rw! z1`g|Mu}K3(XjSzXL4J8ADKKsDOisr(mj@pQ4`5JY(vzaC%{Qi~GUNy$z!ROrK(t4r zV-KNNa*gngINC?WFq@aYVXR(dNX5 zYc93=xT-Xp9dsuH+n^?m@TH>&dPz=%(Y;uscfE z)h$U07K6&6H;15Jpt>q7BD4qO?je`7K`zZy#Tqw-zfjS#d!0`EL2cHzADryWIQeMA zp-FY@{7Y7TpC3mWxbRGQ~gmX4%|ULEWe zz_AbNzh@r0LO`CYDtkx?_6Jy&vPG$<$b_>2N6Gp3kvcKxk)iSJ;4zMXQMN$v?tosE zml~IWqj03@Xs%24TBbPjnnU6#qsO509fWQh-jjZ?y=tFIPIFI1w9ykKt_I8^!Y%@n zoPOia&%XSvbl{9PISd-AUokh%uh7s4rhjRkNAMCPxp2H**LQe-aTC;VqGXE#R%63H z(W)GnFoHv4jb+;kpRb^EeQS5-NPqdz9b;|7&@lk{W!M(im46STHO>$dq0XW^J3UjY z3Kg_oni@Y@Vy>c6B;;2%;!K^!hO2H}3S_jiUs)E(YM0mu)yyH0BJ^~;!A+8G_|#aB zpVv75+>;LJu?wtr9>)ep8SKJj=LkQBgugVZKx_jee|GD$nlwy@a(g3Qh;$At;UPeKO8AS0E5diBEezk1LU0^+AQ446m3m>N{B1o~)XAaxlH< z!bW?`@vl1&=rPb58c=waT%)c2Q|p$EkfT0;dH^eccY+)<#;+)b<`8m1+%o2v{_T;w zCC}tA&`B$Ba>Z*LqCV*p+G2gS5Nth_$^utHp6h%E-8w6j5E`AO4a(DBzG6;HH=H~A z2STe+R;wQF_@Q1nfVZ`t62zp!Z|&~nGo2c{A~AvZ3F`@8kc$XOezZv+>Gnzedo|b7 z#?SBO9bttNdrN0t-@*1^e0qb*kVNQrNj&} zTo5ABPRb##ib?9Uz2-x}LZ2YlQ>{B#RrpEt!1G-@5&d*Y@CDHZ}W=; z>Bi%muRikS%3rr#9qNcRs)MSRq>TU@ok7{4izxl4b@2rwPX`O|!57x`e5czS$0kk{ zRn;2BI=^HTC(n3#1c>c}6Nq775Uu-sQ|TE^b3Ndpha>o6Vf0Jv7BT18@M*>4$(|R& zDtEKaTS9o{+!vul1yky5YrDXT(DTQ*M=X{)gpo;=T2`p*wPCofv{2s>2iW`}bBcUn>LW6M?`~3OQ;A8#b z`&`*Y(ow+-Bq{qQ_VJO7Jx%Aos92HPa8phyc`V>F$rZeuLO{qRn(Ucrzhdk|EL>+o zKCH0gvRj3lq!iUOFK?eQp%^+&4iNR|bL=AO6doY8=_jF2suc#J0b)Ob_~3nMSsjme z6k!(B=qCR}=Wh$g*`dVdj}xPAc}(W#I+qq(;MQM(ynzEC0iFQpNH+8Vir{El&5UYZ z0@ZW(ij^;P^@f-U&xpi0E~wrujfHK0jtw$kI*REKX(w*2@4$caoA#4y*w@4Pnl*Z0 z!oRIO#~r!kLKs|x)NaKTIz19*D(j`7vKaZKn}|80WMV6d!V-66dID(a_*Iy+5mxf| z$==!(0RQ-Rw0@2TJ;6wb%Fgc77n!67 z7&IwSM%%Uz2k>g+f+*Z6jHzZl-y*8QRV|=)hfl50dPBoKyAvOJw8mj`G1IP4%y17x z-p(|o>}fNlITOdn`Q@ITe7m_S#sHE6i&B*J$s^GEktII(jq5zkOg8qJt#IWnX(qb< zTL`_z5<^c-+g3C6$P_#4Jjyew-NaZYw-u)Sp+M zqYg^`)`a0T6<5X}=HNIwHHlR6@B5XPIEQ*VxY(|}Tjt1l)9y6+q(l{)^x+gfKF10K zFP$g`l5ntJ(=|D;!9-`_wSzzg^8xGdIX?2oh=g2*8?ZHI*DaB=m{1q@!axoXN7!sb zCu+o{R%N*GV@6qqgCW1jxw&QM`*B#2fy7^bsVvzQoSbTEZ9L9ta~kMF586H5Ht=&w z$41K!!BqcD8YQkY6fR>+*6XZ(T#+#^-l^|ae`at*#*Xh*5xdl=BWo%E9?-W|WoKqm z8+TL)Q+EIWxW0e?7r?DZ&nk*-fAnz4E#?iQx+ud>0Q|}LZ$mQktP>7C>P5+wFCYQ9 zbIcn?^qjwx9@@!qR16JP0XoE#UL>^~JFZUhDs4{~^mb%_XKCj6z!<Gu2w=w3ACI#S5E!N?Ht7dyP2R`mPjsBitxcG9@4mtCx7gv;u7 zp~a;|@F^jD*`ckiyz72df%yLml>}=0?)v3oybDFVjLCO9IF_n4q9PbhJ_zs)PJ3d4 zt&n1TM-qPa@aTL*MKGNBC$x_&TOY5f2;#sjU*)WFfJ*J~PA4y*o4gd4yI(Lo7%4dq zOfP6%&)}cd)-3#`*7aHfJ{mV+L4RLL9nD0e0M5~Byd%=s4LLgo%VT!;<$Gj{U8x+oJ)Mo6uDb4Jd|6-`Evyt8%?w{b zVnRG#p>2AChlhu235H8w0C1khibL+=z(Ut;A*A=Q4em5#iQ3}-tzx#0SjS%T;@sJsO}Wh;Qs zF}UkCobJpthG4K{I27>=m``xxeBBUsGSL~e>&oA7+7%BE5Azfh%sYV;2zK6ct5#CQ`{q0lvs-U)o?RBwrAZFMQK!M?5?{tfL@Q_8<|rEwJ3gN6E&oN40v* zxN_bxAbBmoR{}WE5+@lS0Q@A<^f0C`*=C8&3=)czOk<|y8kZ+oRmaxW-u0f|bG(4h z!=oJ#jFc1;i-#C|gwwt>!#fJe?^RbXa{X944-XH01cTAu5Qz_=Q5TLF(_*oTUuiPF zV)h^qCIJY6Fb+)_E-eEgwgCJ(Sj5420oO0KRKPxy6ct6Ylc~f93=Gh+o`CXLO@*QE zXD{IM@MtHDE1G+>FXev-VAnK6Fkuy^0=KSTAKm4&FCHEq_7Ds%7!O6&HbIN%%lNvA zzZp7cbubT&6}(L)g$+; zUuFl?ZA3T`$Y|$yJ%EKW9k_CR{EoLXc&>+shlhs=ybPa*M>{4jZ<^nq-TQKam0zMCq4oOsuk0gc*=2O7p6cX}8X7nLYPc(@+_e4`k zM?HX@O@Gpd%=mzW{6Mg;rl#hO_nrJ3czAetcv#2(2X|XiM6Py&*#H0l07*qoM6N<$ Ef1b6ph#T|mXw78W3p+IpcZUqVy*8)X@mjXqL6?b>nKYgF? zpS2*sTDiG%?#!8e_TGo+w;D>=7~~iL003J>Sza3eKnQ~W9EgSj|9IH-GEZ=F$*02x`&-$?Ht@wXKgPz!#`d6&G)3Wqq3X`f`hpn~p6TaP<2nh5<_YJlE0O*-<#x<0&F~F3E@>6{FxqvUkhI{j-Q|L^_$EmNeEd-&9)W& zS%d?>k!2wGziSZV$G8fkf=5f0)Gj-eZ3bk-tjW=oGfhwi#Nc7R6$>~FM9)Vdrx1!K zKv+T9{i@TEM=NWnm(u)~C_VCjV=HmoSQP~j`6)=@#oB}eF=~JRE|PAyRJ19lqP*;& zk?R`2)7-E#Xd^L5^2Hm_8+&N4*w?mv^)J@DnDRvw_Q1z?RR?63$_o9Bh&}`SiFj}K z0gcXPW2?qTO%kmUnPn})FJpj#tU5Wy=7wAU&^yb;#=Of=Z zT7=z^Ep55=u!ynqT|2Y})v9j#Xhe7lK?j(x8C2H!!MhH*>A&^DQ7V~4DJN}s5P(2! z(eAfQ44-(S@>V3^>&({3L!B`Vh@58FzjO4rTsT2RFZj5f@a{1-A2 z{OB}%Jjr5jiSQXcz0ttm1BocY=Sl(kDPIBuOg8?#SL3x54!Hi0Po|9g`EqNkz=sOg zK)-*3lqpy2^c|pAz@7H7uhT)iXYEG_LXpZ~n%Mq9^Oc|If%<}QC=q*>;wzQEmlqj4 zAATlIW&_0S(s6NbQj{4{o10_k^SQ-hm02x``{n0*f0)u%v1H;tKEI+OBC^dA`~Gi= zBhqTBUK}SZ=R^`tCLn5B`uP47Y;cglM%t^+!lrBhW2?z3Q_yQ#s3`)K0G48+w*I&M zc&R{7-v^-y2PY0fe>#;cl|CI{)-RYYfPmNNmM|Yl4#)>k2C?J4p~gbaJe291w)qG- z!g8?&R{)xLhvPyaem@H7TgKM9@**RZqzdZZZM3wHIOi|G{1#KJ>S!XqX4#)nIdsI? za)hRzIx>)}YmgwM`+NZWeZwK#~M7VE0%; zaDX`|wIr)YkU4~8lht$+o|608snBca=O1hk{@F@Cx$k_`2^#R6^j-eXW(<;PiLC_C zV9?6>wLJZWulH3j_nH_A-M6ev>|ovlCG+|5#Gv+8(Uu0#v-lw`v58pdPzZad`xqv z&4Q;?Z{zXe+%m>SI+_Gbkoll^Xu#VV*}KcykNA3LQ~A{x z<+EUJVok~4j3K&uA}6+y8|UzCYl8sk2M~v&%@rI2*Blco2cbVNXzO3h%P4dO{JHqH zot9zBLC39Rjudj|!XKr-FeVN8aD>lj^Q|-OGOt~RsNWvBu3D4neD zu&ZeNgT!TBz?D;edCBn=@`4Y(A2OO}PP__(J%L~U@lsJrc~<-U9vHq4T5Z@ex}w_` z!-&7*h*1m@O^N_#_Y9ZoZB_47n~*mqn4s;;j1LRdox4my04T(jm!m_5am?AlP^_Qz zg<66jhN))p`$J6oF_0aC{?Mc&A z5?)YI^;M#I!$(A2gE1*J`I_Ci_e1Af6p=Z%{lNTF+lHX*DIP!z3%Ponw_)j1iYzx8V9^^0K_`v`;SC1!v8P-DKa7h69a zM>tSvLhSFqW7l|GhiKfAlM8CRLa-O68cd2`N)V$Ol(wHQ-!3%d_z|1&0{HU9n;!>6 zBwtaq|1qSdY76)zxw^LYUiK$TNdGm8s%pr;)QP|~TenrCnx1zTn#$wn5J{?eU^EtV z{{0woyi7nJf=Qv<_no@4zVkUw@r2|TnXdN|+7GDw9s>WABCEJX!&=2QnejiGa1PCB+iU)&-Bpst`XjPh}X$O6i#But6yx3 z5_acRyBwPl-N&{c2nZND%Aru%E$2Ue`>ucmH$?Q={M%ySgUw6!3V$5JY8E@E-7R8h zp>s~WHSOeh?K=!Pf1`>l|E}<}=t(*z?td9WnCcTe={3hj5RO>}7BR&cFy66A92kw$ zc#CBc`XfTM0rnJ+C-k%?9z*;~oBj6S$CGbm2boc%Uk?N_@DggoG=gnl%t1#-=Zan! zGp*4F_|Z17Jz5m}@&&J^x92qgmKKhL3h)T(ow<|YkIow9-+q2r@*m|k4J<1}hbrk1 z%)VLQby^h%#eI0)2No2P)m`T+x&AvEI2;N1CfM>oI~nCtG$H1bi zHG?cBCw@ad!{3lOTopdKFL>}XPV5}hD{HE*1sYAyt*-<>+(oFO;8XJ*C%xT$8R37= zh|kG{C6Q9RyY&g<7sdN+F;iS{enAI7S{PAmorGf9UC6ax6^JF!^6+XTigLPjPU(@v zYz8?uWRb?(_4xJeQxORQ>7f_pk_TT7&7VIf(Sx(2C`0yuST<-E1Nj^7&^tO%XPYfrrhf zQmBJ5XF*?6X(*O*HE2u$uo{2FGiSwMOH*|U>8p=7zW2U6tTcSU+s5uC{I}GT4F&b@ z%`qX(0`6c*t~CmXI)$3_)YUi-Yb#96$-x0b-@rP2D)iC-z5sR6x5xlRu0G$XVpB&9 zQ-`qwT=ynfsYI_k0}g5Mqd9x)=Pwo1ZiO@4Lp!4PZCLyHqUA68iHP`?8zvGWmFVsO z1idq#$b@RxdRAu&e*P+0ee_7A#MQO~isQz{0OwwpYqnsDttVJ(@1if6M01T_ko|ta9n-wK)CVI+Xxx8|LIA%J z(ZL367?hh_Jr5_bLX$gc^9quFIxq7tkzj54f@7P96o_Lj(CMhlD|4RMjQ9zkesUkO z0V?+QXBG{NjlBev7jaju_2;Bsq1f3qWTr+VS=ipP4fYmw@olv7LvG?9Q2`-R#}9wW zU&{@xU=)H#XEcAjdL%fk4s5z0R2P#E3XK~AKyXZoU-p}Q`>++ zy}Fe8y_ZJ4flQoyyO#oxM&=;;Z1sG^RWdNZd@otDKt+ibe}`(1{<=0}vYSZ?6*Pu2 zWdscU-0BuVLe* ziqvbAFGXxwSUqNB)~gn**V>hGeLDS4{TAIqymOBb^81}={IX?nl{)!TX$O zRpk9H-e}f~mEIA4D}EpBoSGSZ`P~myuuwKqP<&4!Ee7IRtG}Tg!-3y6_MjR81qj`O zz8j-K;!5Kh#vm%HO+vVMB!*p7XCgr%Hr+7s7^8!Eyn>RE$+$s>P}z<%$Ech~6Fu@| zf$5r6SYjvB#76nZYq*TW$BXuwPh0ErOVfB>DHihK!?6~iV@~Q%`_!`q&aIDL5v)^$;LLAITNYmkP84>ZD zV^I-N=zT7%3x9(#U%pYGtn5Othb^qXLR)51mR{ujQAXv?v zlKg#GhB6#4H2&AG`L@y9<}i7B5#j7n3du7U*U@&JpFBR%^1O+og{Jspz&@mI@x0J? zw6Q>IJO}_{EwF@x_!F-dceOdwQ3ygM3ziejonMU$X^bLE3U^Rh#L5gNLuyz^=c{cv zqj$J%r(K^9s0I)!bUKPx5@ftHBNwysroC@{$8hNgQ00sn!ds^fCt>y9{4CnA%;do5 zc%!+;KZ|XVeUi^N))=7>;*1nd^n0_&-^05QEHTUm*UXgpX2U8=-& zf!&Gz1B~_T0=hNp5~Fc*$(c^aDO2a-xAo`tXe=)n8$w*EbBm`n8Ri{>ziRm#7&_5b ze|)^g{}9fa`gimrEyT`juY}$n=oc()x>s#j=P3RvwnG|+p}5d_xq!>($&QeBJur93 zoEyX?{k)3kpmJHIqn-bV0ny>$y=e}T^wm*N82>Z0to4u6c>UW92bP73(vze1G2PH= zxR)1>a*`dyuioh`)zTBIc5)gy(hrOQq0$+M2k!4ncz3z*YIK9%`>LrwYv|3cXd5&S znD+3#&3yZv81%Or0}#|vV%xW%Nq}zRD9aG^nMvOB?iMtO+xh_Y( zq598a8Idq8zNEsC*=Ve!NJki%0hh^)OECLAjyICcU*9h~zY_fEg&$lGRY@z%SU}P8 z?-_wvF`0n{M%$i3`UYb{dVyLF^htMZHhI-@J2rp|ykOg_{XdA?cCacCpTb6&LY?3F zgxdA0xz#ggc=?r=g|R#UHi~s-xVM_DKH*nyH_t|~Tyrlb832F`p{iuXH4{k8n7zgy zN*aH?P|aO8j*;#1g`pMo5cV+MG%y5+RcU**4EvhM#AK|!A8h6aW(2#@mS))vZJ{^Qt}H9xH8mlph)0JfRO;GYF!`dk|v1@jzTUmHe&mC^S;{{-Hh=3 zMuA2cDzMb1<9i}mHHjcbjq{eW z^8+tX@TYfh;PSzC5`!+~K+A$VH@)F1(kXvuH|| z(Zu7^Ull#QyT1S&#}yuFjobGk?|=8xEMWw#Hcu~f`Fd2j>`rxpPPbb|3JSc}a;c%X;Cip$AJ7G>3e>@W%nbf zghSwFArG*pd<*z2DA*}dZt*&ZUEhTqmv+~SQa^FDCuqI3>I2sKITvQno_ti>nMSx6 zvOkl733!W^F?b$9tQ!0eYpF5YNl}Lbdi3YccJpj2vSO_2HgA+yMJ|_a>Wt{Q&Eo{C zo0~X_rFd-}QAyo1>SY$Kv1uAQ$;WaE4gVGdax{Wa2jogzK*0-z4gryK-)cDIpF=p} zkO9C}#`3QtamTah`78NtIl_r@~#ejv#>`h%R_w?|E|xZ?ueMw5JDabH-Xc&p_m>bxjkXKbfM0N>}Yexl^+ z=IdUx)k+!STGr&t4gR|U(&FCRYCI zBl0JEMw)%4S64#GZblse-xD&4Md2s~WUVZynuThf-}ifW5Rm6?sjQS4NwvQvI46Lm zwfxEdM;iiA@aA$)JL9ebv;yPdPZ80*E!+b_0aEK;Sj4=yyhVdt87_6BwsJ~D=lcei9inX$abVqlL9e-4(-azx2u`J-yP-p z@@r`+6Wq`u!a^)p(MvaK6X}vu<>h*fZ`uu7u@TzsTnU zXBX0k58X1*Ru#kBEEJ4x-St0u5~_SXxcqLBfBMR3aef~m3M54AJR`+cAwh-G+vz6K z1F}i&ZghlDi#}pGsn+|(u$}3dm^9x zdr0C+2JqjlwfbV9X>D7#3by|-M3K3E;-!qZ3pjM4v~SB_e!ICvY^ZW65Ng}OFEr*) z4&%~MM604-N3AQnKEC+^J>JRYeNfHsMC_ zVj?U_?8%YGG?|L^!RL&|$D_#IucL?zN-!cH^wPMKT{!y)^Fd()3I+Qt#!8PE@;pG))3^l7A4Q-IgfC6-gXrz!&Ws z-S`!_6+%nANyMXy;g1ta9QsZyRB{yd9#0UKH>E(6^2kkbc#S%eO2^Y$^+V^gpcyuW z=})NpYSZBJAE7d7Up6KH~iR~JRnXE>=kDrmOn zMJdSJ`1R|ixC3c&T(kAo&vTLMNH3(GyE%O+3Z1m9yM6(R@E;n|17f4uJBGZ^14npt zzagyiWOk-uzcs{gv%gj29P9qsl*0$%lYQ*dt zf)9flclhY~05u_7=xO0rZ;#m<7Tr!_yOrr7ua2*gDDOqi@}+M|g_MMmdbkbOCq2Fe zz1I^qB)}Pah?}s%1su0~UKc1clHx{&8-(3i;mweL_eSW0K5*9mNdTwN|5-fhdSzFj znj9l@v~4w^lJt_k`?TVRd#VkG=1nZ*Uzk2*qT9v@mwa6Q&XL9?Jo&b$V>?A_@dRH| z`m)U~j)1P`QJ#Lj0kVyAx``OxH8s{$EJn4O}jTerm5LWoei0`2e-mCD_ zLKQwf4)lQ{r8JJr3rWP4gYCa3;iqvBniPhD)m%NjZjz(j>R9>hPOsay4&!zwF9(bR?~IP!=2T-}kucV@wT20=ldeID(O9w8_W$ zdNSR|%5U8A)TTiK8hoPOH4vR$Vn&VR zYw-96ZgE9)5=a6G%_F_uZ24qN<#7y}oDHiFc0WIS=@2~jL)#XOzk;%E$*B^=gJBfV*1eF+#(Ipw(@L$}}(Ixz(>CenD*KxnSx7|X0-ILD5dSF?c4_7VDh6c zNiZJ5Ube_1oxBt>=Lp$#)!+R1*{J&obee`n3)4C8)4jO38k&&uV%XhO2kwvo;Uq<` z>AH+`b27?PD#OM`>r>)*RQf6Q+kv>h7wj1Xr}{yX7Tv>ArV25wgyY}Z#|ip6ayXJY zxt3&wTqz^@`3hu1dweNYe&-9e)M0-XnDQ#530Coc5nfYeOt#K=2x%CI28Dm+xc|oP zAJ|K&BeEn_pw$z$v1IF0#TjL zH_u6My0--dvGm`1c()Zfi4x}KDV&p%WLlds=h8Fia{aYJvxk{LvcX`FI+%jl(hi4> zv1O#(@SgB5#Te4Y&93cpm7=+{(Vy)+Vl-wmgE)EJD0FqRe{>Ik?_xwV6o1@8NrPFj zQLFo_VZ4H-wMzBa>!4ig0SDUVYuwSTKIFM_4TQdV4OCBj=tV8NU;%CQoGB-{16YzD z54;~N9fk^h`&~(B^RzYdNZtyrJc+Ff$^Mi=6#hdV{^(J|u6#pO)j%GtEs4aJ+XEfj zGhPwFd?pTG&XX~+>FDmT8`nvnpV%SjlnMXDWo74if%+gk62yt{hz>(|qC21iNovtr zlvLZO=i^;#jm$`fp&aV9ph5}@MFIgC%J=@OyTZrDJQ!luQmAJyz*;+cLTiFw2p#4X z2d+Kf$=4fJA}JNiC>vV64`lXM29F3w6q_9&w*DSdVb-*biAQpt#N!L}e7s*nx#ftl zH;9>c;q{U?&y=K?bz*KW*{vXx7(oz$ByZ*^y5N)N&2F!*?ptceI}rxQq12MLd1|Vd z-2GU9?DIY z!iBSZu*BLwE#mYXd*uIn0V3uP54$_ZVoE!teEbH4X7+>wq(e6;TyCs*&uDvCDrEQA zDU{UKpMj;86_nYDTnxM2q-7H7!+kw{6lk zmzS#v#QNw98c~yKefp{AI)?R|3&kUg4mUO*;*`tgJmFn@(7+jsKns1JJH;ccp3Qxt zisLNtoxehwm6+2%nR#GxE~@X(+><=QhFfD5PZ&gBM0NAO0lHH_MGlIRklltEXB#S)hhf7x6Y=oGN|J1t(ZQdJf$rp>LfFBRcQp(Vyw%x|2A-*N#~_U9vI>g)#S zHXpnA8)wNc+ZFdDvroPA)S^_?od&M`%|%^TcH4VoAt3Q1v8$)`5iyOyX#sJxyYN}S zuDU3!1ywO-=w?QyD~R8l9FYEuRTUXEGQ7Q6Hx>3)oCA_sR`>3SgSFou@SCZSY$OBF z!zhh|0C=uljF&ll6k`d+&_P)S ziHbc%Vb$FZ{*mxFgkkSX5+sKV{v6V3ZdQ{CvuDM%gerq@bW+ z!htc^3mDcHAc?!B4dmqE*54?r0NUP0`lErQkt^;+MtIad?Ir`Y2sL)D%*0zaE>elN zYZ;#LqPm>NdkkZ_I*_Gj4OS*=2{C&IHI(ZpW3HhIW$rVlJbdWgv+zP>s@-QFwbYB{ z4!7;oWAdj+o8TF*5A(gs?N>9lK4h;8slC57(xFVdXd;+7P4?w|`sAn~xrSp%PBEL_ zKRvCfbUfBBH`0adfm*r$ctGiiwWmQb*;38HAC}w( zuGcHIHxqb6pB2{p9Hil{jFimJK)>h_8xs%y!Fe(S{Us=y<#0bzOnyiV$1XCj1YN%l?`P+0>8r{3~pl0McXd&nyKmJTx zvx~!hdzg?F)l2V{2YXK=wx;ukiXm#(s&vC6D_wJGw&*7>PhV8*itFIn;KoJBB2ELw z<5+LyB+{8WyFUNbfzD^Icz0ybjmn0=nYtOqnfBIa3 zw7%d2sOB#@AzlhN?$n%`l*5>nwT(e=vfwm{wQN2X^1o#k(z#TNmtfFcBfUIR-wMs!wh zs2+&x4oCfQ@VVxK{E7~Kex-~jcK|=#w##7pgQx`$S7b8`XPq~SB)xjll^Z|fMFX`= z{t$5IHHC2Sf5u_K>wHC)pBu$uIdO@EAT)`mX%1ZFxJdhX zIc9{5g~k50%gr+HEt*i>+(%E?Z2(=6e+$qJVTy{SLYyk4Ct+bzehi%^qxiw?6N>jV zB51nhJ+vlF%hKb7AcJt(si)Z+v=`udezzDH%)B;ihii^}3ZZlh`5de;58Ez?V6r_`e5XBE)bCviHUZe`#KQG{3%s^f+=R_G8?zSubovEvL|YYKA>jV1 zyuC_h@?37FZRCpXPss#6Uspyh0iJ zwXvR7RC?@j6tyVvleA@41Uo0QVc7MjPv88#}yA0sE8sV?+q)C^Se(Q zLn>nkBmphtC!8UT7iFVwgfURv z`IYfnA208#ATw`e{lfBt5?;`6Hz_O*0q1Z?emq6j4kjkag5a<5BHv}iTI4V4w2#P) z<}he~yoB_7jd}Su=7T6c_B}$y@5Ouu@{w9RvfBINRqH5=3oKn(zc}CaF#|k;dkjp5 zWq*~Jz!_mjb8**|jCSyn!Ks4wXPlw?k2L2ylC=z6m@JA)6LEl%Z{PCeX(5KpF)sk{ z^{?9nLve2!NWL*%T9HjX6)q5D5i)S=46g$PtW74IN(Q9SR^|bDyp}zLYMsuo>&~6m z@aJ03>UrWH+v>qMS3{A2m55>)QA|u$1t{(g0)#C9=7A*=cypnJ@tiYKKeMb4W{kv2 z#+1FKAC#-fdLy0xM1(vxP2~1XmlyAS6U*=eQmWm;g2I=$cr!~pr9tK#nuZjo=_~e@%1;r3 zpCg=eaC%gnoK*(ahepApy_k#Hr|LLQWMUi6z&s0pyVPI{`LJaEh^+j*Imx;@V0FJF z4(=Wrv1!aMSvl?54YscT#BW;hmLs)W?)``6X(?|7)_NMlXBqNSA}KW3C;4W>`rEF1 z`FhRY?rsjIU6Ae>WpuomkG-Dx{OuM$iFJ;nUC4$@BU0ICH4#0&FMWveC@Fgyc`??K z5j{Q2vrF~$}WXW)ZcKbJ{WXw$`XAj|HhQzT-A@Z{)VA0$>I|JI1 zJAEbg8NcNmHtR_ks{?tS*tnu>JsFB3V4zU(p)SX`=QlGJbqP zSIpbO1%4*%A>knl`2w!rUsL2$s&2*ns=jMU{btX2)T1(1mg1KjfLD+#geryIqG_m! zPQS+d2=U7f@oMkv-&D91oP?#!iu$;>d|`bZSGVqU!lJ-|f<1P-B*u`&Lz;=0Ha1s< zflL4vcP$vAg`WKJYv1F=bTwAwNPB!u*Y8=o52wNqrEns{UqH^Ka6=(3Oe4ka_uD@u zVSla_H-?JAb9_W4m~+#+f(Ogojmcv%HHEnRfB>(_g#bS)z$i8vR*ti^@OGx*4~o@6 zm%34pxbbr>pA%{ck^pMSXbXw^u4=X0dZR@(q#I^tPStAz{z7aJo0lgz!B*CzjNvjJZ zVqR0NSB+do%=NPRwv{$XwGE*RXb3Qey{PocQVD#tv10B|g&1njczz;UKC}Q>f z=~$W3Q0vd;k(2n9Xs7|8Rx;`JeURRkxT>=u@6o!Ep^m5L-j{Bs5C;cC=RJt8U5O!w zKQ7dd{CBM9l)&mUl494@46A`5v5L_2Og-s$sS8l3X7k_lNNh^a{UYS0(l@`sTUX8~ zlP7|#X&RRu#O{&3TVnCg-FoO2a=inr@|QgSAfr_Lib;vRU9~xq9NEz^3J#5EeUm8x z^5dfdAf%yJ5aO(|8j+C&H*#nyrg5(b_u;&T8)aQ|)tB;zN(6ZmA)N^3qo6N}vRi2u z_RIb|hjO&ePHgBchAf>zx&n?WJ8p7vPSxa^5|}-3suk91gjSQiz}pi$VA z)_<8<_VV58F*}kEEot@!uk=E9=+&=*C%9ao>hkJC1S`=ATv?X=XR|~cf4A^vFn6SNa|Dt5bp&Qj;Q0uv4nYQWL?q zZ)kRKH~0s7?h9^&45#8FYhjw$in3BLuxo%mwp{i&> ze6aMM?SeI|$KtB*Pi}t?ceWCg(JBV$Sa4YU>Bp6~=<)6o7l z?3m#cdt9LYX8_}xd(X(F;J~9A@yqB)eschd*FUm%NId~!`Ox-ItuKw@ks`3M3p3fwfHMPFzh|TyL z`cdLmPVaGn&SY~v@+QC^n0QBF6L0#cMszt;a0ob1fEh@qy3A^0Hahl+5YEyFGnp>} zz>o=SJpq}&)F^O0OM*lvr3O)WShgXZ*TWGmhT~eDZSwWqvePyr{4%HiAeFAO5Y(ym zs)1J*Km8R8+G?yCu>k`AG-s|_KgRM;<->>{BIEfngDK(o(VM|UL^4yeJKw)^l#Hq^ zC)o!i!kSXE{Rr_&45Y=4{u+<`V}V**13H)J2e)Zo#=7Tg5J2X{ijTa%=s(&;{EbVr zO3K5Pv-4R&x8Rr&|IE?`b)|lD>|-u+6|QqbY+HF)eV9bg`Ualjif)K(&F)pz`0Dt4 zkn#LyoQB~ndkOzFIUGy_MpQ41PG!>v_r^Zz`#VvS7RmABz2h z!;An&Qw-e9&ipLN)9(tBpp5-X+EUfv6w!@^#)RuH{~e0KzEK(j$282us)AvsMtnY` zUIxrD!-a8W-15X@Mz))c?*9y;SAAbWWPkt2iI=oMCKHu6N!;SB}^D8S@<%_ZZR@Qagd~E|=?NAE;7U z*)WwYO*#^gIb?V(hlLy9sJp!k%@p&1`Q4@WGZ(E?lg&`h4dxn$Hkm0p?A)eP>I=)a z=AYA8$&U|);(sV(n7Yc!%Txoi;*8-{VE?a@OgM~>&&YBih1*YY!YR5>lRG|qEWFKw z<|(~#e4%tyr4{5cPF(KE>n$lkpN}Z7rT|K0MM3S;xpLo|rq^wtTUc)lAOpC)L~%;} z19}2&9V0M5th15++Py9uiny*I>j7a6>Hf9ttmnP`NdMUrVAiVpgxlke+uUS9jxowN zMOQw?^lG^MoE0$Oy>ri+n%XTSeOsk>jh2DZGfVau#Qe8Ts)X6Z%IQ1~o}DC1m|qBW zQbKh8Y?kctX``mLFCwj{0?GlG(L7<)_hpqi9TYGMcwflc{(|vAaZhDrtPnxnBN_3u zL-h+MM`uMrkxzE0-s=ye6C~V8$~eDwE^b7~!YTYb4G$7a3L>d0VpvDndzEYu?DiuL zx7iM(=Ji*Mj6?!`VF|-Bev4c*T=w(ZLc{JAWkzpLQS$cp=l{ZP6$_rOk>-BeG?}bKY6G9+I(S_$aTX>t;D#`WlEdgM- zcWy04Ny=wDN_F_T-HHo2qAGJ^E>b?5zP%|SEY~U$(@;TWCaUvL{8;FV1|16{Xfl&9 z&_yyq>^wI?yF97gk(-G-M4eh!7WV6!C;MRsH(`YQgzSF~L2S&Od$L1SB_8unfxWmj z)5Hj4(e8NRQQ!Z>oXI%_0+1}Mby#MkFKtPW>q~_PBCd_e6BH;jsj6QmnT1q7FKbb% z-s*by{uv1>(0N|!(IL40Oog?HQMDO@A65`6VJg1VZ#_F;sic&ei^ImoLW7eYVv=YI z0C(hen!*}Z7x!|lrhm)IFU~A<5`K{qrHVnxh(1982UOP*%NDHOkha(3b&kMtv6+vv z->gnBm`plmizigb|U(B zy(?6<=@g(ZCVmUg=9t$jILxZ00fuTHrB%yiJt;7^YWc{HTT&oWM88VS(H@ocSRC|H zqLqMfa!bW*I=~xqqmYPQFs_-0aRY6-r}i98A`)HCxe9CWO~%^-`;Ogh0Zuz3BM*R*jMkh1d|kovVJ+!_jd^yeS^xN!8Xu&`1B?yelu z&cs~RYI`CApwnhuS=;HgJjzrc`&ZGz*)fk5t>c}6;h~Y)+2H~jgKL(QtY>0W!x4p>#D9rnwS4-xQL=@XQZ9ori|RhZ3n4Jn z%VnO=ZL!TvQkaAvuI%8yVGide=sp)I3r1H_>&kn)p2o@d+8RHb*n@pe&RL&accqT7 zx<_aM$vWxjH3%v6bYs-ElOxSa0g=R{=oV=p9X#4@ z#(vwTyXmi2@P%n(HTJF6tC3imw$kKK_a3U!-~I?r6+PXWVn#1}mU-v|*Og)M&SuO| zb&GVKoKPx+rP&(gw4daplwJ2G~f>k28EgN@MkF+9tTV#I?eJk#%azyCqQT zcx-R6UUqh(;phA|(%Bm|S|!$*J8#W6SaQ3emc4o>dXPWqXS?L(MNE<$f@fM~I%>{2yx#9hZalD$Pe6^I2BLY^hdWzT=Z78n@K90Vw z0gCGh^Y@tY^40BfkJEs#b|TmTvey!hVlKVBzp(gCDBRReGVWUgmGv|iSzMq-y08Zj zzJK!@E+dnj;!5xlW?j5F8J4)m(!Ptx+xpaF{Mw5$;pYn~nw+#Bp4ha7wSCdqMA`^6 zcOO^U=8-ME7LlqK)4Zf0_5R+qAFEY;Fm1M?eA_3GKrazKG3Qp`YFOUSmZZ_%g^!PQ zBmU^bRY;7fpTDP$XdAZ8S+9X*&6nLvB`q{{4bt7eVW6HY2+@0F(RQN8l0w2Y`Vdyl zKvMEwh6RPv(-r8uUwq|tzgytJ{RHZal;RX4J7K4obFZh{5#=oiHnT~ikxt+TK*JKV zRLTFjr=K~N3e-?H%mF^kU=y;-JGi)C-qXGC=~KNAMX>Zo&bSj|poAXMv@-vE^X}X|ouj1o#6U7&~#~cWe zMDWV>RJNhZ6Sn+$HBge7gtM3`oI3mVdPMv_O#4oWP(@30&10z2tawk|Ke<|T55NEj zt+6uMFyJYE-$p?9za}JrCcv+-^Mgs9_OuTjkA=J%Ntg!^&{lJNf%FN=AVaVLy(CC@ z9bWJjWTc#*^mB0*(B9;tcTuWzJZw55AA{D81``J;?pyMK+q?TTF~$kjJl;N_Qd_ZRo=mw3FL_3%nJ9CmkLE(%zy~m3WgUUR*st;k6Dwp3{15n#=-Yua zQjK|;LQcjR2E2IhyUZU3PeY?So~Titl3`LaEkW8GX|1HNm@F#gM-mN58#dr=ep0W* zT$v1+e3W?hSlqZcYBPXh0TP#4LKlqNJFq_4(kkMNtGz__N1|=w_ z5ZN@r8fZ@S9a|YCuc}0--Yny=wy987hKpEi}-V>#?tsoqf|~L4W7Gn zN(EC>G=C%X>4G%rz-*!fgc}BAucrpI%`NlZMeBmPfe88pj0ITYa8O+h8dzKPeP75g&&ujs9Ub=rDZ#AQ zJZc9IEKKk_f4eQ7lh@ec&Plcu@$=Z;RC>{9T*oIfWiCs6+Ze@BXgzroxeGA7V`k=B zUiV_@C@4QrRAw2&_b7?5!wuil5R3RsfUaLZKaES;^71o%8D;0UcS>G9yrc7-m6J9y zzbeG0`$t*F^D`fT@p=tV_^(UQj0`)7=TfD#XgAtW{4N;uY;=J#Fi4-Y+91JM_ZYyF zG{?)4i}PVv^%{cBU>rGEdYCmulPm!^ZD3(@rqGpSJn&tS_TILXJ{FrRXx&vWcJ5i5 zx*5(f?4xxxuw7t5K6|65{@haS7Ps|Y3hvFd5zP*G%zzUh17Sfg1_sTFHv;fx%E$*i zMjD6@GJ+KJWU8Zjp;*61Z;1N3=j5S&O6IcfU(*+)z+SlDxq~SX(*Hjv&81q&#-KcC zrhdr+FV=qWnChV|+I?(S~iUNg~HhBzqipNVvQM}zJfpKPD*7(5IIgnYCQzP!Id=Wl?(UQggZ2)UGb zR?W}>;0~8aEnW$nFM+Id1Fq#+c(*{RVWq^e* zrbgr>0XDX?YQ*{rHq2li{DB6s3lCCWjQPFpU9eGnzc*tYA@suQ))J(*U?jJ@wx&f2 zhwemx9cR-{R!X*f&&ig7?YXyv6Lx@7W2$_*s1I@IsYGtXMko-6px&y!ul_$+XnOzv zWZQrL7a*2CwETN~5P#08LQz>mlrR$bqZY+egsU75?gzWW&U#-IZ&FG1y#r<0=Wyxk zqu|iXHvh1;O%rJf>072>JsKLXcaz`>+)o?4h~IScqW&bX3fg7)*kHAFygJGr`O+{) z4A=$u_~wThhQFitR=WUk|H0bY8XzDAhuJi6a<@CT&K>bmZ#bN9AU~1%T~W5cO+0=~ zu{pw71bMo<)Z&6?e5wx(i4_Jp^+wI;{J#Jg1n2t?6lIH@ zj>pY(DEWg2{rSBIec2COb=^y}6D*&FD18wVZh7D7a85*3lV4)szW_H*IMD#%w~vS^AOlmx} z2EbVww`@Ax+CER~=^oHc!q?EScC?}t1)1f1BAms{1NH3k-0F$hWrp^9R~|`GakWJj`&z>=j~wH-qpxSgu1^@tdHEjMGs8fU0h| znn5dJsnDqXFz}v2`Ab8g)3r@c@bK_(EwNbgDG;vFw3x7A^PA=KPZI6k8b1vR{{Vy# zY##P-3~z(^bC?v-d@b!Xu~_O35U7^XZy$UI3u+vhE)tC+xPK@zVkR8W^|0zT1m75FSA;^9@W{+Gj@TtzNy_w|n>K zBm*2zK)Yrc;+#d-162Q$)$I=VC6@eQnD}!%19f*gAhjqka z8-50&-UXIB*?hRQy}ZY3#+{Ah!1*O05dzpM&E7Ww#NeZcF)p;%BAW$6Rg-r!P^WQu zMk?}*>c#V#^!o7TGkWrpW$%OQ1`Q!@OgOj5$Y4kG5#DL0^<`-$8r<`>N`4=`|rGS_93TZ@$m4l z2USg;3qaHK9wMJ!N^ zfh;c3y1uow{mGh|1#L!rc?q9~M;{{=OWp>;18`Qs{#};1`=qWs4-XFy4_D$Pd>$Ts zkWNiZ+{3_^pz$ieB3i-tArSaMBr@Y*0sVS-czAf2z)Sc%Jo+$ob!+^kLrU%d<5K`G zfnC4Tgts8^%RnITU`53YR}UWd@bK{PI2kYD^YG{=G&F3SqA0nKGw5mn7s0$p`YsSX zON@lyKj#k>736yHQV$Og509?#5 tu val = LabelledCollection(embedding_dict['val'], labels_dict['val'], classes=train.classes) test = LabelledCollection(embedding_dict['test'], labels_dict['test'], classes=train.classes) - print(f'{len(train)} | {len(val)} | {len(test)} | {train.X.shape[1]} | {train.n_classes} | {train.n_classes}') - return train, val, test diff --git a/quapy/method/_helper.py b/quapy/method/_helper.py new file mode 100644 index 0000000..0e5792a --- /dev/null +++ b/quapy/method/_helper.py @@ -0,0 +1,107 @@ +""" +Internal helper utilities shared by quantification methods. +""" + +import numpy as np +from sklearn.metrics import confusion_matrix +from sklearn.preprocessing import LabelEncoder + + +def _get_abstention_calibrators(): + try: + from abstention.calibration import NoBiasVectorScaling, TempScaling, VectorScaling + except ImportError as exc: + raise ImportError( + "Posterior calibration for EMQ requires the optional 'abstention' package." + ) from exc + return { + 'nbvs': NoBiasVectorScaling(), + 'bcts': TempScaling(bias_positions='all'), + 'ts': TempScaling(), + 'vs': VectorScaling(), + } + + +def _get_cvxpy(): + try: + import cvxpy as cp + except ImportError as exc: + raise ImportError( + "RLLS requires the optional 'cvxpy' package." + ) from exc + return cp + + +def _labels_to_indices(labels, classes): + encoder = LabelEncoder().fit(classes) + return encoder.transform(labels) + + +def _rlls_check_mode(mode): + valid = {'soft', 'hard'} + if mode not in valid: + raise ValueError(f'unknown mode {mode!r}; valid ones are {valid}') + return mode + + +def _rlls_joint_distribution(posteriors, labels, classes, mode='soft'): + mode = _rlls_check_mode(mode) + posteriors = np.asarray(posteriors, dtype=float) + labels = np.asarray(labels) + n_samples, n_classes = posteriors.shape + assert n_classes == len(classes), 'wrong number of posterior columns' + + if mode == 'hard': + pred = np.argmax(posteriors, axis=1) + encoded_labels = _labels_to_indices(labels, classes) + joint = confusion_matrix(encoded_labels, pred, labels=np.arange(n_classes)).T.astype(float) + return joint / n_samples + + joint = np.zeros((n_classes, n_classes), dtype=float) + for class_index, class_ in enumerate(classes): + idx = labels == class_ + if idx.any(): + joint[:, class_index] = posteriors[idx].sum(axis=0) + return joint / n_samples + + +def _rlls_predicted_marginal(posteriors, mode='soft'): + mode = _rlls_check_mode(mode) + posteriors = np.asarray(posteriors, dtype=float) + if mode == 'soft': + return posteriors.mean(axis=0) + + pred = np.argmax(posteriors, axis=1) + counts = np.bincount(pred, minlength=posteriors.shape[1]).astype(float) + return counts / counts.sum() + + +def _rlls_compute_3deltaC(n_classes, n_train, delta): + return 3 * ( + 2 * np.log(2 * n_classes / delta) / (3 * n_train) + + np.sqrt(2 * np.log(2 * n_classes / delta) / n_train) + ) + + +def _rlls_compute_weights(C_zy, qz, pz, rho, clip=False): + cp = _get_cvxpy() + + n_classes = C_zy.shape[0] + theta = cp.Variable(n_classes) + b = qz - pz + objective = cp.Minimize(cp.pnorm(C_zy @ theta - b) + rho * cp.pnorm(theta)) + constraints = [-1 <= theta] + problem = cp.Problem(objective, constraints) + + try: + problem.solve(verbose=False, solver=cp.SCS) + except cp.error.SolverError: + problem.solve(verbose=False, solver=cp.SCS, use_indirect=True) + + if theta.value is None: + raise RuntimeError('RLLS optimization failed to produce a solution') + + w = 1 + np.asarray(theta.value, dtype=float) + if clip and np.any(w < 0): + w = np.clip(w, 0, None) + return w diff --git a/quapy/tests/test_functional.py b/quapy/tests/test_functional.py new file mode 100644 index 0000000..97e2307 --- /dev/null +++ b/quapy/tests/test_functional.py @@ -0,0 +1,27 @@ +import unittest + +import numpy as np + +import quapy.functional as F + + +class TestFunctional(unittest.TestCase): + + def test_ternary_search_binary(self): + def loss(prev): + return (prev[1] - 0.37) ** 2 + + result = F.argmin_prevalence(loss, n_classes=2, method='ternary_search') + self.assertTrue(np.allclose(result.sum(), 1.0)) + self.assertAlmostEqual(result[1], 0.37, places=3) + + def test_ternary_search_multiclass_not_supported(self): + def loss(prev): + return np.sum((prev - np.array([0.2, 0.3, 0.5])) ** 2) + + with self.assertRaises(AssertionError): + F.argmin_prevalence(loss, n_classes=3, method='ternary_search') + + +if __name__ == '__main__': + unittest.main() diff --git a/quapy/tests/test_plot.py b/quapy/tests/test_plot.py new file mode 100644 index 0000000..3f5a5a5 --- /dev/null +++ b/quapy/tests/test_plot.py @@ -0,0 +1,41 @@ +import unittest + +try: + import matplotlib + matplotlib.use('Agg') + import matplotlib.pyplot as plt + HAS_MATPLOTLIB = True +except ImportError: + plt = None + HAS_MATPLOTLIB = False + +import numpy as np + +import quapy as qp + + +@unittest.skipUnless(HAS_MATPLOTLIB and qp.plot is not None, 'matplotlib is not available') +class TestPlot(unittest.TestCase): + + def test_plot_simplex_smoke(self): + rng = np.random.default_rng(0) + true_prev = np.array([0.2, 0.3, 0.5]) + cloud = rng.dirichlet(alpha=30 * true_prev, size=50) + + fig, ax = plt.subplots(figsize=(5, 5)) + fig, ax = qp.plot.plot_simplex( + point_layers=[ + {'points': cloud, 'label': 'cloud', 'style': {'s': 8, 'alpha': 0.2}}, + {'points': true_prev, 'label': 'target', 'style': {'s': 50, 'color': 'black'}}, + ], + region_layers=[ + {'fn': lambda p: p[:, 2] >= 0.4, 'label': 'high class-3', 'color': 'green', 'alpha': 0.2}, + ], + density_function=lambda p: np.exp(-25 * np.sum((p - true_prev) ** 2, axis=1)), + class_names=['A', 'B', 'C'], + ax=ax, + ) + + self.assertIs(fig, ax.figure) + self.assertGreaterEqual(len(ax.collections), 2) + plt.close(fig)