From 046945598560a00d094af3064c1980c37aee5040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Dupr=C3=A9?= Date: Wed, 5 Nov 2025 15:07:07 +0100 Subject: [PATCH 1/8] exam --- _doc/practice/exams.rst | 48 ++-- _doc/practice/exams/td_note_2025.ipynb | 309 +++++++++++++++++++++ _doc/practice/exams/td_note_2025.pdf | Bin 0 -> 65086 bytes _doc/practice/years/2025/index.rst | 1 + _doc/practice/years/2025/seance8_ski.ipynb | 144 ++++++++++ _latex/ensae/td_note_2025.py | 10 + _latex/ensae/td_note_2025.tex | 140 ++++++++++ 7 files changed, 629 insertions(+), 23 deletions(-) create mode 100644 _doc/practice/exams/td_note_2025.ipynb create mode 100644 _doc/practice/exams/td_note_2025.pdf create mode 100644 _doc/practice/years/2025/seance8_ski.ipynb create mode 100644 _latex/ensae/td_note_2025.py create mode 100644 _latex/ensae/td_note_2025.tex diff --git a/_doc/practice/exams.rst b/_doc/practice/exams.rst index 1a1895a..8bc8360 100644 --- a/_doc/practice/exams.rst +++ b/_doc/practice/exams.rst @@ -14,31 +14,32 @@ Séances notées Enoncés +++++++ -* :download:`td_note_2006 ` -* :download:`td_note_2007 ` -* :download:`td_note_2008 ` -* :download:`td_note_2009 ` -* :download:`td_note_2010 ` -* :download:`td_note_2010_rattrape ` -* :download:`td_note_2011 ` -* :download:`td_note_2012 ` -* :download:`td_note_2013 ` +* :download:`td_note_2006 ` (recheche dans une liste triée) +* :download:`td_note_2007 ` (écart entre jours fériés) +* :download:`td_note_2008 ` (pandas) +* :download:`td_note_2009 ` (pièces de 4 euros) +* :download:`td_note_2010 ` (centres mobiles) +* :download:`td_note_2010_rattrape ` (plus court chemin passant par toutes les villes) +* :download:`td_note_2011 ` (noms de quartiers) +* :download:`td_note_2012 ` (prétraitement de données) +* :download:`td_note_2013 ` (coloriage) * :download:`td_note_2013_rattrape ` -* :download:`td_note_2014 ` -* :download:`td_note_2015 ` -* :download:`td_note_2016 ` +* :download:`td_note_2014 ` (distance d'édition) +* :download:`td_note_2015 ` (investissement locatif) +* :download:`td_note_2016 ` (régression linéaire) * :download:`td_note_2017 ` (broken) -* :download:`td_note_2018 ` -* :download:`td_note_2019 ` -* :download:`td_note_2020 ` -* :download:`td_note_2021 ` -* :download:`td_note_2022 ` -* :download:`td_note_2022_rattrapage ` -* :download:`td_note_2022_rattrapage2 ` -* :download:`td_note_2023 ` -* :download:`td_note_2023-2024 ` -* :download:`td_note_2023-2024_rattrapage ` -* :download:`td_note_2024 ` +* :download:`td_note_2018 ` (centres mobiles) +* :download:`td_note_2019 ` (machine learning et catégories) +* :download:`td_note_2020 ` (appariement et mariages) +* :download:`td_note_2021 ` (épidémie) +* :download:`td_note_2022 ` (multiplication de plusieurs matrices) +* :download:`td_note_2022_rattrapage ` (brique de lait) +* :download:`td_note_2022_rattrapage2 ` (dessin d'arbre) +* :download:`td_note_2023 ` (pairwise distances) +* :download:`td_note_2023-2024 ` (compression) +* :download:`td_note_2023-2024_rattrapage ` (recherche de motifs) +* :download:`td_note_2024 ` (câblage électrique) +* :download:`td_note_2025 ` (comptabilité schtroumph) Corrections +++++++++++ @@ -67,6 +68,7 @@ Corrections exams/td_note_2023-2024 exams/td_note_2023-2024_rattrapage exams/td_note_2024 + exams/td_note_2025 Exercices courts ================ diff --git a/_doc/practice/exams/td_note_2025.ipynb b/_doc/practice/exams/td_note_2025.ipynb new file mode 100644 index 0000000..bd56c5e --- /dev/null +++ b/_doc/practice/exams/td_note_2025.ipynb @@ -0,0 +1,309 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1A - Enoncé 5 novembre 2025 - comptabilité schtroumph\n", + "\n", + "Correction de l'examen du 5 novembre 2025.\n", + "\n", + "Toutes les questions valent 2 points." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q1" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(45)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "\n", + "\n", + "def distance(table1, table2):\n", + " return np.abs(table1 - table2).sum()\n", + "\n", + "\n", + "table1 = np.array([[8, 7], [18, 18], [6, 8]])\n", + "table2 = np.array([[18, 18], [7, 9], [8, 6]])\n", + "distance(table1, table2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q2" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(0, 1, 2, 3),\n", + " (0, 1, 3, 2),\n", + " (0, 2, 1, 3),\n", + " (0, 2, 3, 1),\n", + " (0, 3, 1, 2),\n", + " (0, 3, 2, 1),\n", + " (1, 0, 2, 3),\n", + " (1, 0, 3, 2),\n", + " (1, 2, 0, 3),\n", + " (1, 2, 3, 0),\n", + " (1, 3, 0, 2),\n", + " (1, 3, 2, 0),\n", + " (2, 0, 1, 3),\n", + " (2, 0, 3, 1),\n", + " (2, 1, 0, 3),\n", + " (2, 1, 3, 0),\n", + " (2, 3, 0, 1),\n", + " (2, 3, 1, 0),\n", + " (3, 0, 1, 2),\n", + " (3, 0, 2, 1),\n", + " (3, 1, 0, 2),\n", + " (3, 1, 2, 0),\n", + " (3, 2, 0, 1),\n", + " (3, 2, 1, 0)]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import itertools\n", + "\n", + "\n", + "def permutations(n):\n", + " return list(itertools.permutations(list(range(n))))\n", + "\n", + "\n", + "permutations(4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q3, Q4" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 6, 8],\n", + " [18, 18],\n", + " [ 8, 7]])" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def permute_ligne_ou_colonne(table, permutation, axis):\n", + " if axis == 0:\n", + " if len(table.shape) == 2:\n", + " return table[permutation, :]\n", + " return table[list(permutation)]\n", + " return table[:, permutation]\n", + "\n", + "\n", + "permute_ligne_ou_colonne(table1, [2, 1, 0], axis=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q5" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "((1, 0, 2), (1, 0))" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def optimise(table1, table2):\n", + " best = None\n", + " for p1 in permutations(table1.shape[0]):\n", + " for p2 in permutations(table1.shape[1]):\n", + " t = permute_ligne_ou_colonne(permute_ligne_ou_colonne(table1, p1, 0), p2, 1)\n", + " d = distance(t, table2)\n", + " if best is None or d < best:\n", + " best = d\n", + " perms = p1, p2\n", + " return perms\n", + "\n", + "\n", + "optimise(table1, table2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q6\n", + "\n", + "Si $i$ et $j$ sont les dimensions de deux tables, c'est $O((i!)(j!))$." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q7" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "((0, 1), (1, 0, 2))" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def optimise_vecteur(vec1, vec2):\n", + " best = None\n", + " for p1 in permutations(vec1.shape[0]):\n", + " t = permute_ligne_ou_colonne(vec1, p1, 0)\n", + " d = distance(t, vec2)\n", + " if best is None or d < best:\n", + " best = d\n", + " perm = p1\n", + " return perm\n", + "\n", + "\n", + "def optimise_fast(table1, table2):\n", + " return (\n", + " optimise_vecteur(table1.sum(axis=0), table2.sum(axis=0)),\n", + " optimise_vecteur(table1.sum(axis=1), table2.sum(axis=1)),\n", + " )\n", + "\n", + "\n", + "optimise_fast(table1, table2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Le coût est en $O(i!) + O(j!)$. Pas nécessairement optimal mais beaucoup plus rapide." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "\n", + "coef = np.random.rand(5).reshape((-1, 1))\n", + "coef[:, 0] = 1\n", + "print(coef)\n", + "t1 = np.ones((5, 1)) @ np.array([[5, 1, 2, 3, 4]], dtype=np.float32)\n", + "t2 = np.ones((5, 1)) @ np.array([[2, 3, 1, 4, 5]], dtype=np.float32)\n", + "M = np.array(\n", + " [\n", + " [0, 0, 1, 0, 0],\n", + " [0, 0, 0, 1, 0],\n", + " [0, 1, 0, 0, 0],\n", + " [0, 0, 0, 0, 1],\n", + " [1, 0, 0, 0, 0],\n", + " ]\n", + ").T\n", + "\n", + "# Il faut diminuer le nombre de solutions pour n'en garder qu'une.\n", + "for i in range(5):\n", + " t1[i, t1[i, :] == i] = i + 0.01\n", + " t2[i, t2[i, :] == i] = i + 0.01\n", + "\n", + "assert (t1 @ M - t2).max() < 1e-6\n", + "\n", + "m = np.linalg.lstsq(t1, t2)\n", + "print(t1)\n", + "print(t2)\n", + "print(M)\n", + "print((m[0] * 100).astype(int) / 100)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "this312", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/_doc/practice/exams/td_note_2025.pdf b/_doc/practice/exams/td_note_2025.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c9dece42d45d511bb40a42ec4b92bb5a57283813 GIT binary patch literal 65086 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

y?2wLtOI=djX zn(DQ+t`EoR?-t`Cr zftBSoLj5TkC=jhak2b%__&y8QF07yK84--fZedTkhh zK(W+=4Ss*GPux}&^tTONXlFh!&GIZLID}kzN(c;|kz+kaG>@T{9KtQw~{{CT#Td3q=Vx}NKBsmnOdn$J&OD9_w zvTpzL<30P^7J2YYhX?rh|AZ3%y*5JxK0<1K9~i@5##d*;wlb5I|Jrw{`S1HKS1|br zA|fK+08H_n^wa$NjF9`u)atb>pmVUY2;&5f`lpbAAW-rFZj?2&8}^Zwu(M`YUnDaHMVPP^rJDK;bfy=^Q+MI@a zcd%KA2Swr^roQoyseM~iHTs=2HRY1chSjcq*^Z_r`{^fvebxGjx4i5;$VzO%KXRYG zuxn*6s#L0wkaND--tLG&q2V$y7@f$T`tfr8Osf`8rr4G(j~U#lbTq>dPDty z(6sgOLsnm;J}lPl1rb4lax1Rc=ii`I&!I!~*~5?+)`IPviSP;1i|B*kXlKRLQiJow zwOZ-A0!F!`zIpo?Z&B(|G5(=M;$Hht$HaQ|y_On)F(DAwmh0dUHPTDF(G6y6LOv zR+}TU;~&l9H~FLUZ54igUplm}x5N=ozIy%8x^`}@8nJ6cn|$@Ic411Pexz@Q+)&BT z@OxTC%QfzPLx6 zls9NANt^avq0Tm+;sz6qabTNcup%3XgtKZh1{bXXP^U@{1`{^$aI1Hf&E@2^3e*?3ndXfgRoHI22Vkn=c@H>A2ZED-4`gh zu!P0zK2h>(Rv|n}uE0FOzOsA=N5}Q^i&p=v=K{A^bweOi5Wb%(UldCS1D@)y&(bvj z4m;^V(d(w%ELz;xZpeC=u+MVkv;LpHixzqv1UB!;rv+b0b)XfKlkHFjTT^GIyOsk# zUSSiPXSWsr)FD=5=;ql^K>@H|sX+1&Y5PbW3YKdqAk*>+3Xrcsv^qd#9{!z)VbVd! z@G=n|TIRhypmP~%kNHVE?(=YxWv-qghv$-x4G+$tW8MJusO%s0y8p4uEFO}`=$k6u zl}!gnBtF8Y&$R|!Dg-roO3+FM6exprOSbf;`ex?4Jw4(XB-Bn4@tLAqPI1f)9@ zDe3NRq`SL2_P6nP&ij1N*}w1A@f+j(;ThwcG2H9E?(3dwt~uw5T_Lz5pAoE3h*kqg zT0+`j70aqBs9NpN--F2DQ&Tt9@rrI!IrPa)**$Po0t0*5{-En>82;IH!5{gd{d7P> zxxDA!rM-f5eDqk+JQ|IXvm+cErCM;evaw}$&kGS=AT1mbL{nVH#Lph_J5F!bc9jhX z$9y7wFDb2U|2$IFxHQ*NQomOz8gGX!*t)%L-{WN_??I_V<`6r-iTv9%R@nOxW^?hr zAkVV7-~#1XVhdQOk6&Sqh_})6i}3Z(tYP{gUWn&eG^pe_8s1f^CEn@XJwGjQIh?>R zRH-@Ex7Cr`DDUx9QbWmCqJav1QBz|BCFC1PeFlM+=mY>$B2YE{{oedRXYey3;Blt; zgoQ=UGO{1mzTD`ey?6e9Oy^ji&k$2dlvH3i{Lp)Aei|a^`EXad-Mp4;+v(n>G5F)? z$FMgWgMP2$lpL2UJ;SKXAmesHcXkneTKFc>wyl7^s7pUX+jdeRt7rp+*f$(nYxrkD z83zcMbCIMLuQ;Ma=gCYPY3sEyvf|ItUPo+t=6jO$Sihu*H1DNC>*YpxN_ST9nVhMnfADueF`2whD%I{!kejURvQZ-y;26+H| z2UY*sa$VP+!JKIlzI0+OS=y-c65S|A_O|a~9qr(?<$7Xj@e8$*iM3OAA8z4Jzb%#2Cv*w{1+NW=>J1# z$e9r{MXZ3b-!OJfUs=@|vT*(~+p#n9Qg&4D{lmOK7CUM2Z2g)0Ma-RS}ALD9*MhPP#F9+pV<1 z1G=>Zu1@+^Wtj4W-z+h&f9zu}BNq51tJA2W^MsZLMxHzX!O}!n)dc#(yOTFi6ovfh z?KHvLS z<^bF^Gztn(kcGvwL{CrfDA2p(da3aFiFW2M?qgobu+ z1Ev0u5dIzd1pQeTjERvITxRv3KnMkSh#O>g1k`$Q1>+OC$n;Dh z6(K1|AGH12tTl03nkAIyP=-k60|bHk$h4A~%okS!eV^?m0>6H9-$kF9`H*t>)N`^s z>qz@lz|te!lH}cljKfQB72_{56KS`bd7oSh)NpB1Z@)JgjfI_=qw5efx06FYo38Tm zzqf}R5yK5BNYG-@4Z<;MXV8;~8?uLv2QGUY?&S`8dH9FUWn7g&?;grR8#o~pVR{-& zt)^$^u~D5%nSM&7oyz6dIM4326~X5z6>mlRcn6Ocz_9)V#rH&^^Go9S=eZ(*SNzZ6 zXx8!Es?)F4I_Ngk8M{rHKb@fByf*>IN3eo})_=lBa1OJxJwj_JeJo^Hc7wm>FIdX| z4XqQvxs?72g!1nEnUc!#pasMM7Y$r6z9tqx+XF6#*f?_gW^ObJ~ zaljNPnHLBVJdD5!e0v4T?0~WPm4>#^BMHXW%*qN5g0lQRgUSBSj{&{*zaAjPe`jBb z|L@sXu$?3*z_&s7@X)8|aGfn-AQK&kvw-yZMGfthzU~6|D8L{});|ccoPQkfKZ?VU z@j)4esB5Xs*_F{RG5#aT5=nkWqVF-pFt>su7ZA8Y&~0;M)8*M#u+0-LIoe(oLKkHp zaug!1^jfNkM~7vC)-jthBrYv;*t~X;*ch;FB#apndhyT?=^);J0;i6=WU+P9j31$4 zA}+lA>SU!y8iBs8&qrg)VUQkLKoPp=1Z|MU&gq;+&kHV{sMROpj--(UX0x}d+-~v? z>eJJzD@afF$T4uG00SFY|HK9Lf4z%X(Eq>Q#fLUfv;aI12uEt#1~LnJ=y^0ZKb%6G z!ozJWqJk`vc%JND-9QvjgbW^#EXp&oo5c6yJ2>7GP|^%2TbeZHS2!?tNvoYX=`7g zqvCpcSBn2YI#j9Lgab8LZHGWhcPll+S>F5xA{$px4qP$7A-_M+a(o?n)GA=W4r_qM zf5!-k$XnE9ncAe{kBU=&^@1G#Og%%8`k6{XvWBe1feXp!P4EE2L*R51bkihycb6Y=P74{K8wN;Yys9C!ggGEg%}p4Nh&PydI|6ei?t7RgLoRWhgXhPd$!c0T zvIk}k5-#YjTSAJMGaHB=TsP%lFL}NQn@Bj3OQx)D=1l>(;eaa;INbLKZpyDirDjmV zpgY2|M*l6wAuL)luH*&U*ZZ`ck=`{&(`!X{SS%hwzEV zD{)wCSRSA9vDdNS_+Eg#;V@?xaaR$t_CWy%X7+)@*k?GkVkIxtNv*fA716TpHLmO1 z9MC^(TV;Q*n`w9-SgD}SB`vy-p|bTN|7n!DAwX0&XonP$Cx>5g;;eIU^3y0s z9u*Pf^n?|dkv%J@H2-d{IR1by;{s_a9<9`%{3!$2zb_IH_m08gE%_fw(Eoc(NrK=J z$rZ5Wp$&*m;&BIbxWaJRv}+0E~;2W7w}}0{klb zL^J_&iCGO<{g^AgRS3@EogGyPz_i2_8>RSUn-=u=k3MRb)X*l_i zg5;H2NbH2@!(lHe62apK*R^Di@qRqtAxL%2VZLw4dSc;Pcy?ZFl)`qC+EhMTKq-QuhufYLjkd5%)PybJ1!2f>wf0=0&SvZw{txJGMf~wiTNc@MGqOf};rda-8 z#1xb-3^cTwg?9i$`V~A3I}|HBMmcmT2p7OVh_X_sgop5WgBpUiBZwe$FB_s&NI2IG zIJj%a^wkG%)W3x(AC%>|z?}hbAnp&O`s{yyE`R<`(2R!lmlTH;_R%zq_aCNVSy`~A zVUQGuV>tb1*8{>npidzv@HSt9YWfktz14eYX}$`C%ruIO48@}yPC>zVvSq>2a~a{C z5g@!F2<=Pc3p*@$f=c2=c?*OWie45}g#4vx@w7Dqhgw0B?|(noKj2KX|CWhpK*7O{ z{1AE6{IPy)kv@W#ee{%z2rXC>Q&vL%nLyUk}2GIg$HBo8MAa^cD6(^hC_H_bc`g8D+! ze7}kc;|dei1j|M54zV?g2g3^!75gF>Hg3k~bFG?bo`b;;qpx37=~9bXH#HkWL($)d z3Zh|UK?AwyZJw??(amiZ1LKNeWdj2q+5P~>%MJca0tQ_J7B(z?7UpDgeBPhika0_S zf-wL3Feu5iNRGNZ8nHGBvgcN+9gKK-u{@4s2!(-T9+^B2N|jbQh{~C7F9C zV5XdeW!HJgT#S8>!;stEbNA@kdOquymVK^?ZdsOn?nqpypOXCap`OUXxFkTDy!F`% zgYQ*@D0xeU)279*8%$l?p>c=9inaq7UnqKC**`N^<(%8UkTxNz0u<+zP1^9}oUW>+ zl3;u%$TEaESw^DX=Rvyjo!!Qd==>p8G1rNX-|*mwPD6&#N;t0oW zd`vvoCr4u(lRzP`Ip);6O+};Z5waaO+O9>>*!`dr!OSrvwP=$R*~t1~^qQ%u$(=7I z^;_|C>V_p(taG7jpW8;H+Zk?)3>O$wMIn5_ubTO4em2ndKZq3fQ-tH-`;I4rQv)O3I)AiV9mO<3U8(Bb5U4DjEq`Pnj7_+6(iFI*6?>v@rfa+TnGf zIg%J9=E=IQiOy)!0?#vvEO2!J17TVJAc-F2^M8b}FPpLvWLGiSKtq9~>Nv3=%%2z2 zV=SNvO;Xl6?Q@Y{KxTNIKWTygI@2AU(Zk2Ud&odSJFDhWqb%pVZON!8mUmb+Jr_cz z@@5@TWSlVxDgv9Q+hsIyph8P@+Gw4@!vTYwfiqW5ZN_Rdun)q(!jimeljNhv?f zguw$7xv0-b>uC*rSBR=0e2-qi)YNCHI$Kj5I!@mR`(77$w|S;SCz)(_#~>{CIny@k zrO%Ah%2@fZ4+c3(*j(}tA3SUwv+`M&V_jeumi+7)Iqn}d)v z^c=Ota-D^k*X(k0VFKj)4`xcbPreBWv?KD8T8t9U3z|%)U^>#HEKT{PZeEtqOp&!h zRpMISy{zQCG8M2U&2yE%{G7gQ@{WV;X{tDcG~pBDcHFmb>~mk5MZG!8MI)Q0S!<6r zL3O`DuCZ8C^MEk%vL*y~BETWmKZrNV87>8-Dx$zGE#e&rebgo8aPhuyD>Z-75t*xq z?*FbA1IjzwfDCbe=4#}Q_7Z`ZAExe!Kg#yhI)VKQsk`gdHb3RTYme5N&DPU}v8XbU z5IWrH!@dJwBkUS%W7wM4bTLH>SEpy^@|KlOMhcdL=0n>7h0h5{1KvkAJ8r!VT(-?o z^%M-2Hj7#qX+fOrIXtt>FwS)xOzLq@zVEXf{QC90;*&B5hC&Dizrz(8JmdGUj{PKlto0Ige=``+T!3$x*@ao z_1~ROZ%ZS8J%5+uP#{;poZ}`rQKD|5%#^apt??mx(R@PEW1wtpKFq`2qe;54v0+^4 zBupUo4WD$?t7bR|8|3U`c$O@~9z!%Hr9k3N1E2FAoUaO9f@MOoO9~gLx5=eTlDBDZ z;fMa5w9!w)!lXd|@mB@Xd*TO%x#VK`3l|Cn`- zI+jWJF)-a6%a7**MJ=#Z9%br8h+-wwTdU<@OVjjp*a^&*6jpXHX!%c4J>&j1j6?yv zg`<&SK-sMua!9X!UC};2U*MGIZ^FwYBojq~c%xvH)T=vQ_a6wBufkJ&vOqG~iau1{ z8Xak2aEYRy0o*KpZo^*%bOh8Rbcn>g%9a6fZ8U&mGjX&Kze==eURkU%;gq06ma)RZ z1?}F~qAlGYU8pZ18niMDQE2VEAyION)Me+ZQk%&lo9U>b!=dt?hT;?%&UqY6E3v_H zZ)ZA;n7@NR#r17sbw<^G?{x~8yn?hlQnv#K$m^#G)eb3HO&KU8MagqIJ#fEl*jaI-t5LD7j4h0; zjocJzC@3>x{ZyOmnW{69t2*5f`@OeVLrue#0(8SM)6dT7NN1FN9mS*%lxrBaCw1Ud=*^-#n_*5CPHYFMyz;oOwIL$Qi_rZhkt;#OCLdDYutc}E=~WUf+Dr~0;|Ay8!(PSR(5b$ z{SS0z4#TXVR~hj0RjxOBM!KOb6l0-3zG0tNctXYY{ehoVbD+o9fG5q@Z`Q-Y;+C%UURKqH*AF*^g`O_>bkU}r$9BrEu5XecK+kGIVB8X%o(6iBSO!KDXClejT zt?Cj@Th0O_ef_*(QL*dvsQBnQGveBpHSE)^f~jtvTwN)b$b^y-IkdMkEN3WgqF(RYp^h{d4eCyC>t&JUwYu{pi~b7aBA$^VmV z@*Dn!pToXK*$D^46LC1Cc%+}(;Np=;8>z&2eyz6CYKfLvgcfu9|Bm23Vx8h1=e`RR z3U~oZ8vm6MWPgfJ3hr+V4nq6^hvz=h@TeStKYAoNdypmB6qcx6f^k}XNc51Fi)%%S zYs*0{hrMVJ)xULwE96ru#CbIm1yL2`{ds)ayQi5*vAX!= zO&os0h`kW$z2PNUdT=O0Jm&bayc~A0CJAi}WnV)4D+ zWwB^T$UMVM9B5*vLplxTK3sH-9VFIqtMCGl;m^&%K`xNQ^xu$+_4mJgas!}xDS|v9 zSdXMICL41Jb&KAdcnb+jN2}%cM_M>NQ;%%p&GnntZcz*ZUf%B+eWs^5bm zzQFID(s;rUiWA=Op_)jANd^c6wy-Dm7ZAi;_Jsx2XN2~AY;tOt>F?h4xKv+oCzcvX zSP_7Bg-|Po*tA!(o{-D()#~FL>5in`4G%9H!wrwBS5vyP=ZyAg5W^-eQ@2&0Pt!-6ue&cN!vl-T?6%{O-w%`KlH9-(duxNS zkg+_Ge zM_ISuXl}^eA^c5J70+RGu*pjx;f~(JD=hdxn?#KTEZ7u@fm2*_fcf0B{z0Sq&js=N zXQG}W3;Qo}j13Az0tWmi2}l8k1cZwGkFAl-&5F&SxDE=uqUWOXD!4*cz9mpT2No7cy=1Cr~${^O)-g44I zyKN3Y<-KEvpX#Ik^21EM>$;>^nSH2Ws_Bv#ptdVI>)VMhG-m9Ka*dUXi_mY3Slw;y zEBM7w(h}Sy4DK5YBGGqJ=`Dc57QM;PE=U1t8{ z8{an`&nKHaOiw?~PeP9(R>F^?uzxXqQ~Ism*5#*%S}nkZp{rta-N zU!ieaE+yi)Q&KI|(#D-cjJyn!rIHY6PPIEC+#t@DG>^d*4IF0%ay0yQ(LkyrA4NRa zBMQ{ksw;#`S8pXLKO9(wZnFFWvZTV&jC`h1iPKkZFcR=R;c81Kb#rz=HyQ+*Mwb_3 zrQd|}z4K^vi`h`|U7lVo#;m@KR+&qY7qxjE+95aT(!@WwLjAeMwrQAO@P%ZpVlx0@ z<|&@4LNgsoR&u+wZ9QJPx3)Uxo~;UIT^=P`!PT8aGP-r|7kFsdX~)$C(Um)efK)ib zUUF32qCDzkLK||~$*vO8zL$E;I#n}B^CVu3%G15@66*@~U@8OJEcdUMiR};8&B+b@ z4e4Yto#dX8^4dWTz){PDP(kk^)b^%!XopU4sf@ zhwFK>YJ|!jwjb&X(w$aFb@y+XPmQm+EJ_B@E#fub+OQ0Aj|Ihl^~HG)`=gA=;yGPy zGg`S&6W$quLgm3Jqr`|5^0TycNiN`BdW9TEz)^jqN%5ZU*T7e#rA^fn~mGADDK%fEDC%$xZxZ+G2d#w(}6IhCQvB>Rk4TjYd=Zc(-qsVtV5I zId7SHqn(yT{u}nAaD<6ngwu1(sG>tJDi7cOTQB~d^f%+fb(mua8oo?rv}^D$Vz=)| zoB=Fu5og0+wWJ3DRy1yR&;gqp!^X1=X6-fGZw2whd$<{oUd*4gMOSH>o2i=fg*Wmc;iwL8NOZ zr~kG3QAzPtNhGC2A1GajNjYfuOYae$QT(+906oS}G~7A;xwI4DM0m`foT)ET;^wkU z@Kw^Ql4VKdl1NV2>1eCT+bADc_dvGzoM_)DgE}|FJc3i zVuWMJM2b#{(Rc{sa5O%fVk^HlEXM zt%OOqv^}6yFK>Tw4CwDZ&kXhJ|#j3<+Hg_S@lquK09$i}w-XMyr8cjc8o7O&_-MyuVJKeR5cWL=hW~VUk zQ|iNa4$U#Fi>8T(m4e5-(OJZl+cVZ*F=VLkrXlghf?|N$(HIiS@e9Kq}O_erXxG+xxMpv$DYm>O?p@u z^TD1P!WB9F1p+m+{lqBKJygo5JPWw91qNrc{gYquzuN=lKtV~+kM21^fEI$G-F`*| z$UkiP^?TqbXbms_ph?hXTf^yw^%Q`5_=QM@R6(hSbWZ|JS6U<-L zrR>U8dO9lb=|*lSw?eWhui)gYaPs@JgFTDFj|^{qymWv6m~dua+E`PP^Ni~v^(p_B zivi@_aG=gv_A92Ec5jOk3e7JN*oq@K5vS=< z!<6q*Dqm2Zb_6j1)~YA#{M8Kt|3wLhyix7wfsJyKuGHqLAbY`1(Np9vM`i6hIJ@R^ zaYu-%-p2)2YeN^%YN%_>>e!KjqpWKKSRS7D^`ea~ja^SpI2+^D>xIk3b5vGTwQ>!z z`6;l}y|G}#pG+yiU$6_$U94~geQvk?T78@vio(xW7VN*Qol=1HDSj%E7WwUxo12-- zx}?brolMQvKzFB93T1!fc6Z^ELZL$q68BC!B@6wIEDkCJLW!%uDlC1f3y9*mN{J&o zKg9ZQGK%NegF~}Te%d0PkpqzxS{pF)o$|E%G+R8zs|MvN^z8hf5OpONc!Td5MqG%! z4ywQB8V1K>!8fvOFH1+!@NtxQ3tB{Hfs+q|+d9Jf2mCxc*CadZV;%|zNQ67U;X^14*RD z0`OXf4}eEO$0K1O))Q!+Yew_CLvcf~Md1>H#YTbqJG0#^f;kY}VFQCN*#BUUXV|zL z=&<3zE%`A*X~;6OOeHs0`fj1106H=EM27#m2wwr_zH=WT=;UyhN+hf^X0v+gD&?ky zW-uuzy>VK6065zur}b*3Zn@h~f2@y;@OQ%en5fgeKbP#{gi|E_uF94bYdS{*N5g?? zQD-qttg=?%lS^a#2mk)Vm*3;tL;c#1KaIhCm?n0dC#sww=1{HMvYOl5rMbc4R=6=C zPO--3ds`imfV7+kxQs5u@Q}B*3fjpuA15lP8lf_piMGW}@k!)@F+!DMv3rj&bRMA5 z1ep;V+(>vjUX7TWu-W;ZrKO(rATt9Y20MgBW_@bh1)dXwR^P;Ezm?svb84=ZWR}<^ zh*&zYnjn)(OCcUwMa(OqrlMbTCvp{t%l@;+jsAIr_D7Xg* zj(q$BEHp7*YUv9S8S9b5WotUY93Yx?4@V->$>p%AnB}3U=2jR4if_x3gh?!8;^8xD z%~ov2*h-IDminDfOX3~fKRdY|IKi=F+&3&`JpS&5PK)c9T$fQ->KdquYUaBRHSU`N zt17ti7I+P3c5WXCo6p$@NU+M#$vXZTVe#1nudca8!F-l8%V3>a>D2JH-^OT0S6oiv z?7m$EYc-08y}g@%KTuBQ%M1Mp^)>iZORdQQc1vyVU z`6hN$IoXq6TkdcX>yBqCucw$A71p<^sya??-0!by#C`mXtXyPkdSI^1pKoajA?_Uk zhj&perC6~zH$g#-#xl5=W9Km8VgvS=tlH=!aMHOSgSK6Qpi7PQ z(QPQrsKqIR_+<%b6Y-)*FF+d+hAbxgqw*6ws5=N9ukdLT@!e=>mjbV4_7e9=u!52_ zC=+Su5;dY*kNNZ?_Gl_ylZ&A))bILPUaCWuG<5azN!=Omlqpy3kc`C~f}@*3yTtyD zJ-C10J>oyg`{+ahVLoZhp0Ef#U5IGX3L(Sbz;+x-#aE>C>Dds%GLLc964r^Wy(||A zLE4BJOpO!msmD`xwr)-?M!c^2=;3=C;}_b-X|CtFTq$x*jl9uT3g33zie_BvwpCnH zRto1ArXntfa3@k1b*6N_f4a!1n$Mcid6Q0-4!1;`4oRPxU=f7ybQqJPbWju?8iisf zQQJwzKA-${ou@MiQcjQa@T|MwoHFU%K)595QnVf!+VtCVhF!H!L<1x{syBUFRT@OO z{QTKUw1XYANjbEL)vD9Oii}EwDp>0&ZYgEa(t5OzZ8yn60!MIfdRrY@RM^&@hST23 z4?q@ZC_f)BAx2xbHqandv*ju--JCg3?d&7L61$HWFuUqWhh`}kpHq2 z*wzeBo&ZV`(7S7SK!=7t^@M|iBRz$Tr2~nlA!2)g8I6#=?|`p{APm`n`M#|9p5x64 zC_D++)<&?xMv%#-!qlhG$)=|egRvROJrKrhp==U~(0wq5IUz;J} z>V_`B-8kU5;XhE9u8-CIHJ`<*#N&}$XaH+T4(*Km)+L`c|UaZ@l|~q%-&FTK4B23A4%1ul-^?rwN5S^V$M!mnLCfT-deB z0xaPYrh&aFwm1`(c->2h+-<`Fx2*vEF+b+6?@OlPpRN$=I{PHSRUI6;3|bZUyVTY9 zsGkCtfJbE&hqG^Ay_Y4F<9r zT9yU#EP5jF*C$6ACarnoOSE;j3@2<%Y)jOeym@M=Mu$PKhgc-Yu1d9>JxwQpGa z8>1owGj!OKgkeYU4-u7?UbpLDKAilX8>nlSY+Rq2<}?l4fcyTtB)duP11YQMRpV{b223-r7FCMvIwV zr<=Y`w&cjEN*F?%`@RX!l0qW-fRBNS;F$ivRDBjyeatfFY;CIMlkRWLgg~J9&8He} z3PBe3h@4j13k$N(aq{)2W>p3&$KudHrqvkq#Tx@L=oQR!;{;6Ah!t*H*CSoMyBlsA zj=KCNlonWQi#Gr5mG_l);Q=_3%tFMIDf(WeG_%z@s_6@`mHH(cy{e&V7C>QEfHVYb z6*221vE$H!m+^C*Wq8f!lRDr^4vxVF-Lb!Wl2bq@8MN{&fCePR_P>Q&0Bt*W;q#_~pw_Cux%sUq!~_akyeS~NV*Ohp zXKg|=yqAlgfX^2yb3S-#9NVNE#-d6+$Iz6$cMLY{eT&h!tVP&540ohwuv$bnR!%SZ zgr+3uhi!t=FRz*1gzup5twN`(`4jRd8S^uFAXRDg0;eiSjf9!dAPEbitdnn3H)qmn zX0p*0!_4-{cQ9SwCuc*AOC@efdce3l)~}A)fP+x%K8WT%YWKzSbYOgSQ+KN zK2;R|?d|^Qa`%7=B(j56Bw96x2-}i!l73f#-$O;Y6XJcydTT^# zDdVNt=Et( zZr)OJh9hQr+AWXUOL2r1WkCF_<;EB}j^hs;oNK>WO_W==A?*?^^HPMhIX}&Lyh}$3 zEZjMDc@%g{HrvUl13EZ4pEt~}t2o~Gr1hn-UK<4WRM8w9FK-9jot+K8JYnaKc&4Su zYsZ494M%?f+1GOfF|Gx%p~4OwM(qQa%!2pO$?j+yH*y~0Q!6PKlf6#W@{Dl}e@n-8 z>a7f^!#sgrz{AH)XoT^09ReQ9Yd|3 zp7e@@KFc=JV8(4LDmY~pmS9R5!6g$$5dn8rz;$1sn3~^x5BEnh(0&?fHR!d$*jXGZRn zJ;h9XIzX$+JKtgNy$?7K3KgD@Q64u<`%yGN=-@h>f#4vr8q|uIi|AbjTExCo zuIh%xGV666j5ifOB|^pgN=OmHslIAScm0*)l=oq88HJZ^kb=)GvQBUx!6vs>!7e6l zT9jwN{i?C`*rgMVu!l}hePWGb*)(&rWr(O71c_=jxG*J9Zq&aI+aFL?U!zJvlQxt9 z_Z^HL8DcUI$Z8nQF1!Mm0(|-w(4vFKO)496KY2rZc9rKAlbA?5-Sbk5J&kvzo`JKw zoN+2+sxOxVT9ZKcNb?Kpl5E8^cILzC?cu`$w5=*a@x}eE-P2pt`;Fyr`ZFeeRTTk) z$tPYim`>}a7-yzWC>J1#Fhih)9Ahb7qX}WPq+F40((v1^LvSqdpuW_iLrX;1kPsb2 zibM?%VGVilkt<~ENrV1Q&WjKDZ)#+KA%cQsbU*9v+M&4{dO4iIWH~R#3vG|gr;Mlemvw+U3Kapu&IRO1z05d`=hIf3c zI($x$vY~j@cpQUH215|K`D|fb7|}bbGYnx!qD^()AyY?`E-<|2c;6R?2K=Z3(GZ$7@M!u15l{>h{RWn z^=(jeD15$b8BkBpQix`n;OFXFb}x|MyZ+8#2^yBwmyouaz(PnExCBr8w0Rli&V=BB z!^8V!>h}-|oB~%5aJ|GI5T@?KtTNE7fac@ZmbCRPw@@98%;_~M!PtJjZ(0aoQV83n zh(I$D%MUBKeLue0qzSPtC_ZakQ(}pFQGX=Ewx%Gzmcf_nV`NV%l@gsalGTo@5`vDb zB+)j|#{P^RP?Ze`kr5Dor9A?gx?zQMoIjTW$kfKD%o%*AG=C4B`2B_?)B+yDv2Bb^ z8d{N8meYzaiD^!3>H`a8Of10)!s#;y<&afjt7 zH%gGi{AGSFmUUUjrkSa|HE{-zw6OV8a+e< zeSu95PR@biN~oC~NF34}d5UEW`|OzH1N>M!n2Pd%8z%jMEGjH|=y8}7NCU&T28tE3 zNNu+Kbe}~E_XB%RcsK_BIQwyeN8En#nA_+_?5fTszd@t^79B1r!v18SqMtEdf}OWi z#sde-m90D?&YAU;E^R#l3Vp~(_4CP(!Mxo1wW{1N-}XSKHHPl> zcdB0QShdj94@3q--3D`PNV;O3xJ?^N&7Yv?!s}A)myU#HWPH9*^j5~oz|Fv;;nO?9 zHx~eoyP73L2$Hmel0#*zzp~x{GyRw9^_bV~y(3+4`8+&PsM3y*5w}S!V5f~j-7o~-%>Y9FSdjB zVy0>loWfUBe}Hf8qKw36OH3mq-j$u@6dcX-Z|qxt6=K`Z{&~iCz?adSmAjUY|F?l(*-uUce*Q;PTs(S*Dm?Kvm7xER z&?rIQ2RnXh$%_|vUI3VM>1lJ^0=&6PE#I%2&r6=PWzsD-sOkV$MlkII`yWj52S$HU zJDv!53Dj04pgTirYJeQ!r}o|ww2j(~E}vHYxpPI3e~wl9DFU6cUzqV=a9O5*&Y0U; zKb4(FZo;9goY{sEFZW9`$Gi>lF>aT5&z(4{^^TS&-#DesPEmsjwuyt@gwtdTy$SAe7bdtLaib4Y*pb_l zJVmXNx@OoX2{I2*pdw@stq`(iluSsX_HpV`Izq&q>B#a!CxVC^3h$6M8zdX5Mpq$IS1T>_%~u=B?UEd ztVqnVhL*5q~wwxktC5R z&lZZ#(3D~@NMk7}GjQofKb|dQ%N)wV#L|-@ks~PONWKzw!Mw^$wL9d$1Ww*G#(j_G zXCLZMoxXKmI1jiFFNRcHz=8EIN{{3rO+crFR2&~wg7<|WjU**OpcHBuvMJ0?MRi9+bpb{k8|!>5Ss55s3|2j>L~sfUSJk`IFbg8|{)4us2Oc>+Nd z#M_FK-U09ACDK|7*bAYpz=eBz=+$<3dikUoaTUs|qvNHsXCCyL_7WZpiVuv5ur-=B z?p-)b5PLHOtZ1{14v@BmZr!UbgX-#x*w92y2J$5Ksbag+(KE5VHB3(Uwu*xAIL+micVzArM9{ za4}XwUoM}L9bove!-T-!SrS1hMooe&r&tf1Bsc-W={D*DjIGy`p?6KaOA#9QrbR)v zHr^CA>LI%KbYv{ncn@<8SHM~=8p>Yi?b9m5K%tqL`=@O`PzS91#HwpsCgL7im$U)E zi|IBSh_{gm`RNHP5C=$JuC0Vup6~Ux`~!e!KAttl>{r+6uS~p%JW#4hTG7IR8AmN5 zbITBfou~(!r)l1vZvc8M%{Y*4km=U0FwrZE6UUa-^CSeGKs-O1W1$VbZ zFg(wXNosZ^c>znWOLgy8(02|~=Yiyqo4QELPzIs>8N5I6x zCglfD4xs9~r{Rs~hwPP&y*u8)YxpBzJJ~Y?<8MtEPCK>BM{L6)%N<^KR{4l`5su%N zDiOFp0mBPdNu8c|Q|uqd!uj}L35j^#A$&n<#j-5=*xW++FuW15xbn(5WTQ)ZBgC7#>G)Cu&Re?ip|ffrJX<8)OsnQQN8 z$vGBF6V0u|n$$!{P0FS%k&M-IL9|cI%CGG#;oI&*!d8p0W+C|vX|{G< zR#7g8OiZ}c+|{nJyB33ev-L}-hzE}Y<4-g#G9x&c1vB^`II~}b(>>uUEWo*ehd0W1 zP0$sG6|hEsGi`+2@rpqPjfD;|N{wJ>P4_lYbEPp+cokZ;fvn1OR#RXo&Q>L_VYQ^b z+|)Ucy+CHXg}Ue2)4Pe=gv6)(qD7GbJq+q$75A}NozmlT%rQ3 zq>N&1<}UTF_RD#n#S3e%XB)ec>)#-bZesW8-annzZLgm7e^NRBO)5i2Z6l*^W#*_H zWYRLQ8%%w^go4kF$$~3mXTlvzmx}2*hodLWMC&v`6s2ZpAJ%j%BWM~m?Hy4y?EGLU z5eU^)VeSB63!t8H&G6{t&N|xe!ASr*Kr=RQy&0KpGdymtnz0*EDqnrY}Dglf6n^BtjtkuFD@ zw_@ZD%jH^8ytrU%TRvCIUgl;WtLHUHdU`NZW%L6MJJ1s=RZ_g*MV_6cFSEP1A>)t?bZC6ZAetJi{{Zxkj7*14Q^xy73LcaU$EsT-6`{duoV~G zKAwktY1aaKQv9^5a%PRsu09u`RUgW;zm+C^P%qaJVpjaYU4@+xioB_C;}anH*-`eO z%)@|G%d&US?K{M@h6KM#%TBu;zdZfB#WL(E)AOJo9y-@Laz?s6R-T{tlpWo^X`Fn& zutNQ=*A=MRa*;DB`J!bw{Kpq~Lk!)|Xn_e&wkV1}5gWB=ve=u@&#klsqzOgP~d{KMrU1hj;pX#I?mdkB! zJI!a(tE7&=8vWZbc5D$;{moJzt292F-qr6R0o3m-T+zQr3)2RQ_=auUxgSk$e3n8+@b*TV?wReCW%A3?fv6pLT+S*1vj)%ZJyih$F0LWUs5_Rx@> zbWOc%6=b($R+0mb<^@-Zl+fhZy3R{QO^eky>9nEv9u5F=1?xC!E!{>@GSuhVT{9<8C>7l{HeNPKGt4KWR0UJ z{D|eDqI{3sEuSP^$gq;vSNtzMYYlHZy1eBjsiUc*$9qK!oHWsjtBhU02AV+olyDM! ze-pWD5lbbiD~uq`HUUe2R@uwmrl6p4b9wO%BW&U8xYyTjZaKa|ruLDrRItQJpk{{j*HkOeeuV`}Ce0WK^k2*fNWVDO#VIxL91arsXBfR)E%zR5c zRU*dY=>{|A_6bP{#6AxlrQc-=uji*@$prTK$eS-lILc!mrpHn?I#8Ru46#WyH|Aw& z_lX9@t!SwD-D%~^8i6yl25)5)DzVnfxZ1oG*Il1vte!Po-y|YSI3LIo+?KL5l+C=b z9b93K7WL+Ic^#%s!jPF^ZnZ`kQHzb(zPw9Fzmfb3a4jerz8e0~P-4q~1tw~7bY}L; zWub^?n+Q@ zRJ%F4-0;@8_9SVt_zqNX6+d)URLQUxXKinpde*-(8xY=jBE-;t-L4pB3h~_WAZ{9( zduO-qDi}4Yi7y5_7zy(o(ed|puqv9l%-`5ts^A9AUfWPE$}8RL!Ni-RtgqtI#O{%( zHdP#}b?^p%t)^(+qG*C6i#p9t3Ci0QA!e_^eVG!d`#v)1sV6`8Z4Zq}nX>-5Liro! z*v&fXuEN8DR9FW%=FuR=`lmIKRqtkPLnnQRIao^wV~$GDSY8N=ob+$k8wc!`c7!T~@Wg~t!*@9WLE3E=LslANbdZRXx-6l0>3hCxVljKchd@9WC{}dNX{E)Ob zN<-l)eJP5JUu7&p*GOzCc;o+6_71?J16{Y^;XSr(+qP}nwr$(CeUEM3W81dvIp4hZ z&%B!XtKLjiQkCv>S9ZFylkTkDYf*g_H06r*Ot+Kg+|Jy3MI(+Sb-jr$J7;z@+{Jta zwKkztslqhD^5VDfYUiX%^oVY~Jkxt0jgP|#TuY34k#4O?Vi{T~SaaR@xH(=kR|!wU z!HTtH@95?&OD8w^3L8|V-Sg+vRM3=cSo=Q^@#IAHT&%|o27(va4QFlSj=qlXB zb(?uqUz$r_Cc(t6g2I7eEt6td<;FmGWthKuf<<4^me_vCvh9gO;hNnBWHjvvIymu5*W(R*NDYwjh+-&BrimIhpWD@8GnCDpBY$_36+s215Ax6t$6~ zp#-0!l%BsVDtj(ar@MroZLzV(>GaYo+SsomBcGnn}*mGuGL95?&w~l9pax zoi>_ZE?rkVr|-^?d8czUU=pV4m^_&A7e+Q7*L|!k2TB2V>lIgX_bVgl?Wm(0Q7Mua zEfOEbMSl>^74o;jrKe{dg3FssVqgJwM)*_Lvj!kTc#ZX7*hFMEI}5fykYrzMK)RUm zTb4ba)NuGv$cjXHmCdlIS85lU9vxRh9o^K)y7E|i?T84COq9ZrxnZa=l2t~ZMGAbt z*{Iow$tR{y6OZRbwEy#xyYd_Bu-+)3@o?kV%F8uoE@%Sg+3}6{Yzz@lXnxh?FMRIY z%v{a&(39~0bLNInN&EITc;}0ibp$I0;VU_y1p;@G2cn&gh0^Gh7f!F~y=kMUt|Oft zn})fmJ{Myugv6ox$N8zo%lSai9lpdVymA1{&l=|!wSu%>s|0L*;7uXc1v};AzGi=Ei9qEHx5_ar4oh2hDWeA5k>T{?m58Hy zsfI{y399E+zTqP4ku$6S1FootOO>u-7IV6(w{v{r&l+S;%}>QKh*jF6y!`9j013b` zi0IONH7_9)mTpcj_N}4^5p)O2aFzN|$Ho+br>UorQa6619rqREama94shU6?9dF+G zpaSbPM3fEshL-tSo!w|vW#c6;dF&=}g%9hTrJ%PzEY6M#;TRk)M*NIX$$oajqNZtj zagVI5h!;JQ8Hzf`y8v1Hvd8lk7oA0Ew<49D!Oeao7WFs|L0pAplS|)Jk-l#KyG4p`z>J0_sC%B!bY2 z)OYsrx|MFOs+*l-Uk`I)3`k|%?ry}ya8FzCFYXPBF72(&w}%jZ#W3kNH5fUy{_Db! zmE|_Xd!lW1Eb2u%S~Ab`YOD=HF{_+#n-enWDGf!48*gtNGJ!W0 zkIILaat<&k6|B<|VDKn)15qZdlg(-Wl!VrZS~(lsu2br#WKfWAW{nQMv6$1R5V2L?x55SpK-C-t9}P<#&QR z-Zv1B=SUb+_`hT1-euGTWiZLuZyuVohq97MKMja)A6|xuYPdXG7HzA;x~9|3>*Wb8 zYMaw>cHr45Jh~g%kEAKl^)1rYqpt@)e=12-#cmIL6 zva#LUE67KX6p$>mk3C6z+e$jPD}Ec{Yq&@LBQY%P=rdSk8Ctb(>FA@~8g^%tFs3vK zSR(JsXHc~OzjC&AQ%z2?>7jL`0#%$dn-$H|8jV}8l8apG1uQKXO@mb35Fivn$5-uF ziU%ymcLF)xq`bqenf%i6{Y|uv!$XF0I1D6xBSWn!!8d;aNCEphrr3c#u>Mm<-|W6{{-DM=luE2|=iFA0FmYwP`KzE=nlK;vh$aQ)A&qFw(yZi+;l8MiqN{mhwhk2sD76{aLi1kq8m5&*zUOgW z1BsEj{rDc7WL0`oj#U#|g^lwU46S+ziz%IPN}L)CIujGexrZweBt5D%(4qBl(%fe* zl6gOD;M9z%s2r7cPy|*xgv~v@zggIQa!Q1#q0d)$IlC?MGvvaZ%26S(Ss7wEwql%_ zo4O(&FJ?v(Ji+c0Yg6X+opKW1!~;vrQyk_QRIz_e7|;dl|0eZ6YfrMWfY!IY2P*1DIcW zE%Q9&9Sz9q4Jhiwd!8A!c76R)MlE~VB8uaegfBY(ydr7rNIr zyFoaDx{!9jlC{yiEK$=iB+S3ulSVdUj8lr+fI<~psG*osEx8B~bPQf&6lK4z=S$QoysVy$3OWY~?w8^i z^}MZ*v-!eo1E=|wL6UP1Da#~n-GrLViGKa@{3RL&3$@a9(TPHtVvOw(DNAb*&}62g zphf4f__!1|yj;~ij~1b9#$B;w#JNvZaPK?#C`Ut%)?HouAnBzk)=SMs==)u4^<5DT ztC3zb@R;};E_-=EMc_W|_p1%CtHN6nsJH`ZuL>Ux;1W9BC4RYF`4yi-yB6HLy&sFi|;BFXK=BlN6I zB&%g{TNCe0;%Y8vtDY0Erav_8oR7l?UbdNKIJdZbc=^u;XGXq=ns)c&Xz{xSDi1}; zi@(P8UYcKJPsvjigVonHj1=BZl`qtxApEyILvH#>_3Wg$24j7kr|TIu6XBGeS9cO{ zwc%-*^;?n7?U{E3AQw`YYLuFEK!%|KBFCe9MG1RonD+KTsVLy^9u$C*i8rc8K*K-A z#zqKI=}6(zPjRc+_m&a^_l}=0<`>IjB@G@r$kC8^H;#}fi8x)w(l;P0{Y>RFyc-sD z-O}l=>nlEf8JYB7_BIbkE@kJga>bMrgBAH&LFX%POE(_?i*>UOO4%TGP#4(|5r+MkSZ6nP5_zbjA&WXP_fqD&7%Vn_nbJq4zxv{+#5 z{wj{x;DVrh?ba6$mQ`<7%PFG=x+w;fY~`KDRp5CP1qXpenvP%CP=%K# z@gY8=qTu!{UK=NavP=i{RnKJotI#C~Pkau^Hc4Jgl@N%kP&n7taok>ynOzVOD?`4p z+5(>!OO|C8c!GA0S1(pd1MPt#^lXS(*|fYP@Z<(WChxU9OxnLOCe~&Y7dUsFKu^ePUBF

JNVD-rslPiPiBm-H8s&@Y;q?WYBw*3QoFNpBMO_~*rTy$SH*^c$8qSZD6`!2T< ztH&5BiUaR%JR9;O*Ij}JG{#(~n?97|P<@`u$ve}pa-N53?mben%s%I8o1b_Mha^HV zklZp5-j!BRQ4{=2u{70ee~te~$7*U3NuyM)g?W<<(Eur*22e^tZo1A!(0rUyr{2|; zv}fXaV3K2%pnTESy1S%Qm)tz+qg>)Z>z-JyF&jD?jy;Cb>&N&?JE0(Q@BJvb%m;8f zNLkCL2=yEKottfKvr~aWQsivmx!}S0L^og*%fkwO`IbsAxhWx?<+^4si1Mb3GOTE~ zg6M%ic3(I4Z*A1lU0$uXib9asWKQEv z(uU+%c-Ij8ofq?@QX&UkK~35U1X*=M|r=u|i3^|8SS`j@!_QvhJ} zL>!y>eauJ~r-ez6e4bnpPXEIFHv_?gXA+~zRBPQQNDy5;O_^+Fdl-kww>~U&7IO$i zXC$tcpI%|^YGVzCr+QlNVE@r3h!>@vIOgzmX{&jd!k!fyoY^C%J<$VHh8S>y77RlB z@;s1sE+5F#0&OyBlUVtrt%Yh&^HfmozFIZazwrvfI6wqjzQ;d48u4j6Sn4Et3XXk1R>HceAhY)(V5k z&)a%rQAF|id4BRTKgX(6ne&lQpY6R>9gQ?+iPzsh1wsSYpIDPejm1hkO>LnNuCA*+ ze;F8sRyN;t^DEYdpZkhuIO}gMNlA1PrtW77nl{Ib}p!)h`n>UaCdCi0@MApu+7 zlHrm>?A{u?CPuKq!ImC(_EWUep35JMpOidH^Bix87Ih0|?Sb4U?mvVAgv00!lRTGC zOnfPoai)CY!=yOT)B5Zu(o*LyJu3+-R8+KW$td_AuCDsIva3%yEmGJ>QGJiKOR4HC=8nu};YK`&ng@CUyB1j;hv+m(`#Xv~l7&qx{ zUx8xggk^J*GecJ{Ij1?&g2Il;L#!5dR{{Rrrm&_-MPTK5>|@Kg0djFKto(KYVw$~( z>2|MJx5X<=x!Mh}o4R?0qL4Y});JZmeGyj5GO$5sMZXer*OlSnk#n9RC3z4(q zO6^%K3<;xp4Hv@dYtIx9lWNDb^z-HJ#&^l9u+s>#vZ%3V+_!C_7{rTM^{)T0^`XWJ z*Yb)3q5jrun@G8}*4P>JPn1+?I%Hr|P@WTY!eJl=HX|9WmL07HnG_BIRZ6s-D(JR0 zwvhvLrqX*ACVa|R;s_kjxU-Xlp zd9wPZeLxq1(&LQHmnn6ICY_IibKYayWhI!m00+39dq^cO9_1Cico({YQPTFSvv53r z!H4CxT1P_bw?SRW^MzfTANmWIn%p(twa>Ub zU@M~rjm<^Vm&-3n5pw;`Tb?Xn<}+8FKEnXMH?&NQs~dT1F!a>jaU#b5U4rXQlukhdh4{)$EsB-evGHW~M`5O89mP?|H_{1gCi zG%OQ6^wyLVI8b46@qm0Fenlc8P@pqW4YRnUw^7$g_RHsw=1!|=jajbOjn$3Us0)wv zr01WmdsuO0 zLP$sfBO6J0X4JA6VE{~AdjM(>e-V;?!$cq;|9pS{p&fo=NksWT_W^7G8(x0k2>*_t zRpf+PUh?!LCQPrXojQOr9yEZ|c=&@i6xe^A{0j75;6SsX`8fr&t5{bA_I&^ra6yGW z-wAliVvDTgO7_xJljZEe3moh+j^w?7*674&9Uhmb<9JlL+;pg-&o=zAvI5LL3l0AYO5`id@;l5f;P>PofQNve zlxw-i8DaXNI{ol&p7~*vz}oS%j;;f^+b1ar69nfGLU@^T*6 zOWx03wh#D#N5R)G4)y_f*jThi+@0KO#{87`&z_!dqD^!>(P!Aj82|jqrAVn3nuPK7Y`^nI?Wj?|*2;hk;># zj*dT%e*8A!(a<+f-~gmF;h0qc*2Sx#;eTLQ0=`jeSQi10_wILeU6?_rKsWNB`3ZKo)`9FOAhJKkgKx& zbSt9&igA7>r{{;00|*r0^WtA7){`Oh!9_0>0xy1`MF9Hpf*{7g0RFuK1&9?#K0Of? z=?CCjoC4-@tHyYYTYSbPM^@G8O^xe*xp|+X5iR2FLRJ%IaR z${PKh(qsK%tc!TODAg0a*ZTN{CoIWYcYJOSY_J~TYH=UBK{TUNLfOi6ar>)PI!hle zV5XI9j?Nl>C4$OHuh0(}Y}@41jE6_`TD3aM;51NxnP~yHv~RI&Yq$M-@NW-tY0{W+ z?E-*UcY-0l#}9#VvZCH*k}#lKS!e2JlNK1au>ZWk*1nLJ6|8Puji$3@O?h+EVBL3>QkN%$Xxe1=J#Y zf}5y&os`orIGbqPCuS+-_e=fZV~CCFjLc3OF%Y5_^> zj=TPw=kf@+q2$sUl9rP&rRN?V&Ht!n&f;I9u)CG^xUF!*b(Z-NP-~qYQGoCi&Z@6oOP*`)wU?L)@Rb`#{K~fH5oq5Q$m>%@-fYXns8{ex( zW2bFqrT1kDnphN(jj*d5D+~&EhK?oDKWXTj9NK4=UiOFm{&K2%2V_}uH*?>V*!#S{ zm}->PMP4Y>VqYr)Hh=Y(w6=_7voA8(OmXW?YNhwK>OD4eth&4$r1n|@_8hixc096M z>SuhSCh@?~G`@CCQt3iAE&wPJ+L*atja-lGhj$;p+P$(J7|>!FTpC4n@0XGuQ!obc z@}01By46swwY{i_`6(893;rM@I~h+~qBtt)#*&)xsozc|a+ye$cdA|6YOqLo;NvJb z&NQq+r@6>Vu&$qC&Gn6EoF<**b*nf%A>@#>E*PDr>Z4c}QLIfG1>w^OU}s83T|Q>* zpg{$-r5|7uNee+-t=Ntr?o&&~*~`1CkQ85Um(3z6PMg4h$MhT6Js=T@BiDdzK``K&W0~vXVW?oW=XBvs=xqvBG9Die7ZN5W?lxP& zPo3*IU<59vUF%)>NlJ5@W4(%UMj=qkU&^JsQf$30nRIqM8*@9gYa@QS9#y&R0||&6=nL2?2)jHbobq1w2Pvr#jhg^rynWPz`hpE0%-W3E4DKUJq(HbH)rJI9UnO{8K*cr?{Sax+?XeAVu8$7;uI17SUmyHIQ+Q+xYz zPSU`y7^X%$^BU;zV6%x4mG)5dZQjf zntQO<%QCCSw}yw!gEnL5E}sVKJ5@$bgI+ChPCa*mUm(g)1XHM+L@+>MGIb`8YdlaY z<-^CC^tfw*-ZnB$o>vH{Wmt2m*YrIlc{IjlqOb`uhPM37IV(W&XW_RIP?=O z9QUB3EdMNSF7Zm#n3h2~{>kA&hcVpw*3d1xmTlV(%>YsP_ndiQOg2$W`7BK{2T%A( zK3$FiUdmL}iBB@#q*PzD8-rBN-60CZ`eu%6ZGVEIFH^d1VLYP`!8wbOQN`V6Y0oKv zBI3QH4)4~b2fAERoBJ5K(W5yB6Q(q?4O_asd95 z&!(Vz4Ygl0zhkm4)kfO%`P>u^bR>HzJsi?;MbRUzs+Nj$po$KZYkw{^-?j4x6cFus z5LSsma(5dQFbVgkO1x_yh z&h@zfD||z}A?+wuoz!lI*Xh(8EvF;W-^iD;n!V;SC;x?ziGx>6JEOdy%(1MXUxIBn zZ(xhO9_Ob`D}ph@aPMqa_HBisFva-Ss1!&Avr_d^a9$fRBkaR#Y?O!tXzs-b%-ALG zkSr$Rl$}o-b`0D?$8iaMZrrj)yaymP%WtWIK@p3KIGN4j1}rltw=BbAzOH{h4XXkO zvH|^0cD@7zIz^fMh2_>*2A?c59^W=@Tv#c-j=houiZ?bN1G`gy@l~bwwNeEL3QCE1 zA{ZiI9^Bm`$hBcNh8aZ@VHS(WB8p`t_e9e^W(CY7X_Bx5j8>ndt^OosfV&D9(A^uh z(cM8_{LLR@6-il<9twlef#{t0G(p`$o1yT0mtMcFjMtjF?fv`IKXj&sDB@P8e$Hpn zUE_$x1jAIk9i!K{3g*iQe9dJwrh?R}cowH&z?-{lR#Kp3{fxCxF;+d0;2X?%Z@WW>XK$}U-No-F883t!BZ%m#==^liqJcNx7%skT)k!R}>6_tg<#+C4bBw5a z(7PUEm6zBKc!|=FYDCq-miPea6z8VfLwkB4Qj$Fr>!$l*E4VfDPB7CoS$ikCu(ua&-lk26{HB|LkvtVup zsiIFZ7a9qY~udI!W%d7}WvdI!G0c(qRh{gLh7iD=A2OW1Fu7saLaaeTVxnSeN>E*I~>Vv4JLKI`x?vPfkmb+f zPi*osXPr78%9|PYv++f~WFMi*EzX4-Hbbw8V&IsyCE&8GHekZ(VG_=JRaT)APwT&i~L`3uBvQyqQJ>x`~{BVDoo{Ad-k&F;SMU1 zV6$k@V0g+FjI|o+lxu$pqdpTH9!D+Am}@tqAfdPGA^~bYf$g z`*++1gafR5npO@I0V_r$svVrw)S|-8r|-#+ivEiLA)A_naoh&NG2=}on6`wfx>8m2 zf=_WXn#+L{K{@Bifz;fyO3A=RuPOic^>}OXM%<+n$&$=pT4fU$|21kk36eZj5moEcV4cjuU~0l*x%Dvvx2c&l(;qUkVS9 zag>>so2O2}RepSb}k%+gzsWcadNhoYvpiJ;5!k0e}k*E^?U6%VypIMI|nv={bonnquxf=fa}qAUmN4pShn!*Gnuew*}n!`|LE1F+`0uXU34 z7=-J`VTscIRDds$L?aRd87X^B<-jYFwpt%UuAA>p-Id1$$>I0%c47n6z`+Z+8?F*Apz3C=%U{ozgn3GNi>{_raQH7*6(z>!cl6Fo*HK?`kV!N-F zF;Z`wRC1V}8qWG*5NguUpG-e80sPEHOqWM{B%zQ+^E8pVF{k$1cXHD*rJn8TTKp(a z|AF-GR0V*^@Mqb%?XP=cPw2EC`87KSMOZp>de(4$1w9p?UeSD8=k?>Y#OG+=qv;Sc zprU*+uvzo>Rz<-!(W`KaA{B9#!EV+8V|ywJIJYfTKL@Hr$!R7kqh z?^9+c>l0rh7J<7&$q{5RZ-ZP~#>?cAJ|93;=9X3K%z>i|Igtse*-Q$FR#EbDWD=3OyvA zMK*;_1%9@2+lnWhK>yb-H(qa{ZCa-R@vEmNA>KRjb6cQN_L{M(+$^P^MyN5%Z!4pa zX2sK!V)Mt*N`)2p1Zi|{L}5cB9x^7VzH*ry?uD|MOQqsmq`!;BPp4kXWn<}DNc*G> zwqZUA$!SD!$G*g{Y15^Eh}^?-zG>&0@r-5o=0k4av>Ui2;Ly$VzCFibJuo>fN z$`JS8&UTJr)v#rE?-;#OGPM^+sw6-YZmfC96Pu^X+Pj z+A-4)q(vYEb|E+yD#?~PNTshMdRV%94Xk~5hbGbd?x5KZ@M2jPixf&Qe3m7Q1lwzjL zobU)Eb{772vHQ(WE|nm#$va6~>(V zPqN6u*r!t{PxcgKqESg9xfm#>xtS-o=hsso8Gs_GN#23^OU$-!iqx91Kj8 zxDta~b!j1rBg!aK|Nc`vuH%DuF#&bzfKI=8>-Z20{&U!B6{CzLKZGp#hs#h@J5#r1 z9Lk0)`WhV`s%59hi`(*Uy}*_GO`2%I@tuXm2;&R4`D=vO=5`GtgRfugu@h{!9}eTb zsyqt4^8UF09;!nZW=0iPhSX2Hu=_k*PA=LE35iKyW}5MF@R!oqtRzNLn&hl5R91Fu z-3hN@N)6HGoLQC%JzQKEH>|m9BxcmOu_>m-tjAT8>2saA(bz`KdR{=BeptgL4JG`6 zkeo;NpfEB*N;mp%>7s#XN^`~=swA3d!$N+pw9irB=DT`7Yeo&8jXMcHaC_vSmS~{x*vi zUK`ZFC;&rU+10~!YNg~R10XUc!}8AI~f*b(zeXAS&wmdBKgZGRGvE=yCWBeGW#MJc9W}h3nd+ApHf~Z z;v!AAG$EC&iA^3_a7KVzo&RyFx8p`4(FMb9m874;$I_oxgK9*z<&y>Xr8TUCfi6i+ z-B%~|Vj;cUa6SQmF?i1Uwwo1M6bvukW2K(?ja`-(+WdLK%KMl5$CDe;m92N3T(eE- z>_YXOkBL{j_rF;{`AgyS|3u92S^s}9z1t|u*zME9cb}-a!=g4yavAL%|(YdY`Btx6kzCm7C=#U{`|~vo_GNM$Xg0hu)hHL z5dYW#7UA2$ko^aS72WY&{DnKT;Vl^f#M43nDhp;+1Nz&6=!HC`bzY$d4F4KACrAL{ zK%iHc5$l^f&qdK^k7$bq??nUA!wq5-V;|0GzXCcRt;Dnmycg zO!#$vpF^ub6+8TkPb{KJ!_6alPC@;1H#urGv<(CULmOdO>68=ac!nBMoC&%IdW<@B z5^8v36RZB-6D+mEHrgz{!zPAv_JJDw(PL|fR>n&TYdO9e-+btj9Z=VWf%u+s$FQlI z@shItyC`PAc{D=Kg?K5?!B6cJONowz!AtuU-CYoyz`tx!iriehB29TV^*A@EQ?def zCSaz{6=-GiqMyeK;d7qxwM6AsY9>&R6V~^P__btVH*L9}E(feP8rnBQ8gDVPKIl5w})+T+p#Chv}MHB za+2oFRx%Enuiyy*ZOFSz0*m)@?Ezye(&ZU9= z-TMP``e5aji$G?>Sym1=%vYJxE4;Mc`}(8INB^~5@><5zoBJkKxjwf#t%i+jOTp83 z7Y91)*Ys}6{kI3RbVohDii%p+f`d|q?}BeS{*m|Ce1rC%(wd(vDf3}hHbo?N`Y1 z$Gnx5zt$enuDG@JF~p20JfNv7U_rTws{_n}p?1Svcl#I&R!$lkwV-ko#aEJ~hZuO# z&3wE1*k2By5BBlC|54ak?LwRg1hYc{!aWls=}TyzB25FYFzKHNn8qG(|HIY<)d?CP zhzdT3beFZl8TB}5KbXZqz9r0NL+J0t*DEOAJ}t#X}DABxR*=IdvYWW94AH%}z!ymOm=GuBTe{&VWZV?JaiiPk5V@%kYd zBCkyPo!g}GBBx!i=cDAnW`N7n=c&?ZX9H}Gr{%g}5NW09_#GFolSs8a&g4Y3j44mzL&-`Zp zSJJ}HR@lJV1fN`(^H(*Kg@K-to}T_!V~mlCo{@~6p6s`rjGgiSu86XufxW$nF+QEB zfwhwfB%Qpnh&rvPi?y|(fvxR-Y*9A1aKiuH{)bJ}_^Kw3PQRDNXQE|bW?`n0LcY_J^Bpv&#ogTC`~b0HbSM_Z>Fb$58|+1asen>-xZtrKmh zmX&4KidF@aS_i&eO}jFY#>Q901QRghOV3b#XfFQd9iVG_#~C+R)B8~iZ67JS^l)Oy z9by}smw!%k%&lTG05qkjJ6?gAZ(cR%Yql`|eRSeWC6q z8@kzVJ!!{)Q&5u)Y^RALru(6%`b9m$UhMSQ@b$)go7zE8X}s@0)nq|W$<77O%AB7| z4Wnw%2!>wbW}c@VN=ytR5$l=nRF7Z%VCemF#kb)&A=ruj)AQLtIl(1!KIQ1ud0c|d ztT7Q4*Q?8yr`b(CQId=By~jR8>qz@6#%$_WTGy*Drd#>Y09|&mG4*`7aIdP-i|(gB z&1P)Q@NMPO{?%kaNB!||auJm^@$`i9VT52Wb?rSkIGyDfszUFos8(-ur<1m{etqLh z)&25qdu8G07U!Mz=Ora<{Znz`!<22QL}syBC%SGKkFQq$tjo)6IZg^75$3?&r?Fex z2ZTL#!V$1aI*n62+p$Lqh!~)9l>`E@=e}(y)zearyBOM`T>>aZ>ec&{$XMJzLPMOI zSk+{OWR}${rL%=YZ~#s8A9`BTabO$0HUOu~jenCFfngx5V7j_GX9Exd+KX8^>z z>mcir?W~hE+i8a^uoR4v4clM#EnDa!neKoi78romS_^YG zoPDB2F4e2*7oDr-7&!HV>JepxMH4_6-B*3-&jAcKz(%t;=uiI5ZFSxt=zkAnr=y@u zFZTqtU8O?DJT_=%S3*y7#S6; zvEU!J{}6Zg0#?k0Fv$U9Us)VE)RUHqu{t~0H`hoAL0(0~;bS{mmx1*GO1c|+yrM^~ z1$bMqwD~9D(w_w+N;`9jk~R%AkPzXglUf@r`l45pIp^YIx)r_!j{&8J90mS8iY-!b zql#UuVb8Q3o^z#&otnpa)vO6>W|0Crss$hAt_Q+4#SrH=qX)tdLZL^`_V3>XT@AtS zqZI~pCBQk;X8qmseZo=bJ)xhB*U9>)US&W2U3;Om1HTZid`-L-$iJ;+PvG>y%o$X2 z!M}eMsz2l0D_**uPRd6U_r3DiiSL30fHKoRmJrC*8qe9^W8$rLCt^TC`iBu^%GQ)| zwpMg+9^_Yti5bEA0+-})&I3XdXF7+JX+8q)KYm7@LNM`XizfA%fII2%Ee_Qn}3_zO&XKH=Jp9A&KTMZP#1d!bq5-kLUnV_$TD*i3}{ z_FDm8Kf1TbpLWui(*QU+PdEg-)Tn?esW%yIiJKsoz|gUZ!#Mpt?Y4t*HneBjo+;2g zTVsKax9$q^N35Mf-Rm0(4(h3OCX` ztSYfeLUo_o1b}4Ttm)ed1)FH$7|TKKf06jYs=@>fgwP2n4&l|{!i(jsljJMY`uy!Z zxa>KVF1%9ZE~n}{pp?g)eCSAIAP+ERYAPNeLbBk;SkW~X?)nO=UzmvPz&i&-lLg!Q znw-y*!4ILSnfBYWj!3ZSJRAgx1gZbRA3;co?~w{~YuHJl2{Dnzyh8@9r}ou1mvORJ zYm;o)LUXrm;dgwv2DzR0SwB_8LRqPusWAD5e8Z> zUP{KtaP(3d1J}2P+MxY;hY3`)59-Z3ixSW-fM=Sr@I>cA))Qdg51*PUGqklC^p|05 zpM7O+d+Rqc|LoV0C3xVvJL6phljaT?ndzJH^HhGjl`~>K;NP zljSYX!~Q#N3kI_Zi`hp0wL^0DLo4<3qWRuSc6PTd9`~Z^+2uBWaN_(9ZtCY=StVpk zhSuyG&zS|G;Q^R?=_q^ewIN4WZYb;44#_=mlz0P%F0M*t1q|Wl#pm=@%XiB6_D5|h zo?{huj(f4Tc+Qr8ciQ3aU4e0D(|tzVv6l<4ISQrG518ow{YUJo_yF7;b1jFVWB2C)YO*pS%6{ z^9M*o@NFA4JzjrcnvuNY*6b1pI*iC4`W|$>Si5g_3j2-`tKBN^nChTD4ZON}-7MQ`WO(I5f;srG|Mm+woy%eUeztGHZtc*qQy*HNNaUN@ z6Eo-Yy5Ww=q^uXFmJB_Wyl=nxj&p(*|GTm7|H=aUKgM_JiYBIzbP~44ChquJ^!N-M zEIN>MN*10b|2c%DQ^VI{z-Po~_{~(bvvdB<{10oG;(uzG+Wn^fGkE?_N*G^@o0XYe zK$wM*RYZiHO^}I+RhWU1nT3s>g_((-l~qKLjTiraU*z{X|AX@ipN0MZJEI%QyodCR z1NP|M$^#llqF$?TD-&}oeMmZ$*zvieaw?HnNn)^J2so04Qcch}`p+#K(AK%T(3DH@eR5yd5tp=81V07M@l_!>5M__g{`GJA9J1F}B zIFdz2#eqI}ZfjxhREReMux_}# z-Ge}IbJQ&-f&wKWl(2G1_%u>bH6ObMbOeGBTBL!6;Vb$HHIKC1LR31aVel}+g$8pQ}&B!rEjVNAL*>Aq~l7%N|ilJpl*X}icLWWp?E z=?)T;QBJz;fswaFHGCuW%NpNsHiV&VLb{vMWGgq?C!$2$@BnGOd4hVjzV!}bREoC> z^%qrYNYqj1aAc9;k#9YvZt<$NWvzH)+nK3LsCp9d@^O@lYmao5!ZJ~U&0Dn}zN6sq zA3r0PtyOZ?wKcYt$W7Dw;hvp$9cwR&7C$5pJK=931^p+wOQJ|>TZfj_cA;eANFCLi zd91=orOOCpuc~bCFByLbqD5#mCVRDDFjBzI<^t^s*AWBm8n< s_j$. Si c'est possible, alors :\n", + "\n", + "$$|t_i - s_i| +|t_j - s_j| \\leqslant |t_i - s_j| +|t_j - s_i|$$\n", + "\n", + "Si les paires de skis sont tous les deux plus petites que les tailles de skieurs ou l'inverse, les deux quantités sont égales. En revanche, si $t_i < s_i < t_j$, alors :\n", + "\n", + "$$s_i - t_i +t_j - s_j \\leqslant |t_i - s_j| + t_j - s_i \\Rightarrow s_i - s_j + t_j - t_i \\leqslant |t_i - s_j| + t_j - s_i $$\n", + "\n", + "C'est faux si $t_i < s_j$. C'est aussi faux si $t_i > s_j$ car $s_j < t_i < s_i < t_j$.\n", + "\n", + "On en déduit que l'ordre importe peu lorsque toutes les paires sont plus ou plus petites que tous les skieurs mais que les paires ordonnées associées au skieurs ordonnés est la solution optimale lorsqu'il y a chevauchement.\n", + "\n", + "Dans un premier temps, on implémente une solution par récurrence." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "8GTzJjK3BoQ-", + "outputId": "21016f1f-a3ce-496d-cbae-9f7aa1907d57" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[3, 4, 4, 5, 6, 8]\n" + ] + } + ], + "source": [ + "def appariement(tailles, skis):\n", + " # tailles.sort()\n", + " if len(tailles) == len(skis):\n", + " skis.sort()\n", + " return skis\n", + "\n", + " if len(tailles) == len(skis) - 1:\n", + " # il suffit d'enlever une paire, on les essaye toute jusqu'à trouver\n", + " # celle qui minimise l'appariement\n", + " meilleur = None\n", + " for p in range(len(skis)):\n", + " app = appariement(tailles, skis[:p] + skis[p + 1 :])\n", + " cout = sum([abs(t - s) for t, s in zip(tailles, app)])\n", + " if meilleur is None or cout < meilleur:\n", + " meilleur = cout\n", + " meilleur_appariement = app\n", + " return meilleur_appariement\n", + "\n", + " if len(tailles) <= len(skis) - 2:\n", + " # il suffit d'enlever une paire, on les essaye toute jusqu'à trouver\n", + " # celle qui minimise l'appariement\n", + " meilleur = None\n", + " for p in range(len(skis)):\n", + " app = appariement(tailles, skis[:p] + skis[p + 1 :])\n", + " cout = sum([abs(t - s) for t, s in zip(tailles, app)])\n", + " if meilleur is None or cout < meilleur:\n", + " meilleur = cout\n", + " meilleur_appariement = app\n", + " return meilleur_appariement\n", + " return None\n", + "\n", + "\n", + "tailles = [5, 6, 3, 4, 4, 8]\n", + "skis = [5, 6, 3, 4, 4, 8, 7, 9]\n", + "print(appariement(tailles, skis))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "e-hpvealOWyQ" + }, + "source": [ + "Il existe une solution au coût polynômial. On s'inspire de la distance d'édition. On définit $T_{1..i}$ les i premières tailles de skieurs triées par ordre croissant, $S_{1..j}$ les j premières paires triées par ordre croissant puis $c\\left(T_{1..i}, S_{1..j}\\right)$ le coût d'appariement.\n", + "\n", + "Si on ajoute un skieur, alors dans la meilleure solution, soit il est associé à la dernière paire, soit il ne l'est pas.\n", + "\n", + "$$c\\left(T_{1..i}, S_{1..j}\\right) = \\min\\left\\{\\begin{array}{l}c\\left(T_{1..i-1}, S_{1..j-1}\\right) + |T_i - S_j| \\\\ c\\left(T_{1..i}, S_{1..j-1}\\right) \\end{array}\\right.$$\n", + "\n", + "Il ne reste plus qu'à l'implémenter. L'appariement consiste à constuire le chemin qui permet d'atteindre le coût minimum." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "this312", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/_latex/ensae/td_note_2025.py b/_latex/ensae/td_note_2025.py new file mode 100644 index 0000000..dbe0bf2 --- /dev/null +++ b/_latex/ensae/td_note_2025.py @@ -0,0 +1,10 @@ +import numpy as np +import pandas + + +data = [[8, 7], [18, 18], [6, 8]] +df = pandas.DataFrame(data, columns=["schtroumph"] * 2) +df["index"] = ["schtroumph"] * 3 +df.set_index("index") +print(df) +print(df.to_latex(index=True)) diff --git a/_latex/ensae/td_note_2025.tex b/_latex/ensae/td_note_2025.tex new file mode 100644 index 0000000..150b264 --- /dev/null +++ b/_latex/ensae/td_note_2025.tex @@ -0,0 +1,140 @@ +\documentclass[a4paper,11pt]{article} +%\usepackage[utf8]{inputenc} +\usepackage[french]{babel} +\usepackage[T1]{fontenc} +\usepackage{amsmath} +\usepackage{graphicx} +\usepackage{enumitem} +\usepackage[lmargin=2.5cm,rmargin=2.5cm,tmargin=2cm,bmargin=2.5cm]{geometry} +\usepackage{listings} +\usepackage[dvipsnames]{xcolor} + +\lstdefinestyle{mypython}{ + language=Python, + backgroundcolor=\color{white}, + basicstyle=\ttfamily\footnotesize, + frame=single, + keywordstyle=\color{blue}, + commentstyle=\color{ForestGreen}, + stringstyle=\color{red}, + numbers=left, + numberstyle=\tiny\color{gray}, + stepnumber=1, + tabsize=4, + showstringspaces=false +} + +%\newenvironment{verbatim}{\begin{lstlisting}[style=mypython]}{\end{lstlisting}} +%\newenvironment{verbatim}{\begin{lstlisting}[style=mypython]}{\end{lstlisting}} + +\setlength{\parindent}{0pt} + +\newcounter{question} +\newcommand{\exequest}[1]{\bigskip \stepcounter{question} \textbf{Question \arabic{question}} : #1} + +% Informations sur l'examen +\title{ENSAE TD noté, mercredi 5 novembre 2024} +\date{} + +\begin{document} + +\vspace{-3cm} +\maketitle +\vspace{-1.5cm} + +\medskip +\emph{Toutes les questions valent 2 points.} +\bigskip + +Au pays des schtroumphs, le schtroumph statisticien a décidé d'organiser le recensement +des schtroumphs. La première tout s'est déroulé à la perfection et a abouti +au résultat suivant : + +\\ + +\begin{tabular}{lrrl} +\toprule + & schtroumph & schtroumph & index \\ +\midrule +0 & 8 & 7 & schtroumph \\ +1 & 18 & 18 & schtroumph \\ +2 & 6 & 8 & schtroumph \\ +\bottomrule +\end{tabular} + +\\ + +L'année suivante, le statisticien a délégué le recensement à son apprenti. + +\\ + +\begin{tabular}{lrrl} +\toprule + & schtroumph & schtroumph & index \\ +\midrule +0 & 18 & 18 & schtroumph \\ +1 & 7 & 9 & schtroumph \\ +2 & 8 & 6 & schtroumph \\ +\bottomrule +\end{tabular} + +\\ + +Au début, il s'est demandé comment la population avait tant changé. +Puis il s'est rappelé que la langue schtroumph est une langue parlée. +Tous les mots s'écrivent de la même façon. +Le schroumph statisticien va devoir réconcilier les données. + + +%%%%% +\exequest{Implémenter une fonction qui calcule la distance entre deux matrices} + +\begin{lstlisting}[style=mypython] +def distance(table1, table2): + # ... + return ... + +assert distance(np.array([[0, 1], [0, 1]]), np.array([[0, 1], [0, 1]])) == 0 +\end{lstlisting} + +\exequest{Implémenter une fonction qui retourne les permutations des n premiers entiers} + +\exequest{Implémenter une fonction qui permute les colonnes d'une matrice.} + +\begin{lstlisting}[style=mypython] +def permute_colonne(table, permutation): + # ... + return ... +\end{lstlisting} + +\exequest{Faire de même pour les lignes} + +\begin{lstlisting}[style=mypython] +def permute_ligne(table, permutation): + # ... + return ... +\end{lstlisting} + +Faire de même pour les lignes. + +\exequest{Ecrire une fonctionne qui retourne les deux permutations ligne/colonne +qui minimise la distance entre les deux matrices, en déduire la case qui a changé.} + +\exequest{Quel est le coût de cette fonction ?} + +\exequest{C'est beaucoup trop long. On propose que calculer chaque permutation séparément.} +On cherche donc la meilleure permutation qui minimise la distribution de la somme par ligne +et par colonne entre les deux matrices. +Ecrire une fonctionne qui implémente ce raisonnement. + +\exequest{Mais c'est encore trop coûteux} +On cherche la matrice M qui minimise $AM=B$ où A et B sont les sommes sur les colonnes où lignes +des matrices de statistiques observées sur deux années. + +\exequest{Comment utiliser cette fonction pour implémenter une version plus rapide de la fonction à la question 5.} + +\exequest{La troisième année, une colonne est coupée en deux : une catégorie est divisée en deux sous-catégories.} + +Que proposez-vous pour y remédier ? + +\end{document} From 77727b3cf02065644729fb47cdc1004fc4b8a710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Dupr=C3=A9?= Date: Wed, 5 Nov 2025 15:08:07 +0100 Subject: [PATCH 2/8] remove --- _latex/ensae/td_note_2025.py | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 _latex/ensae/td_note_2025.py diff --git a/_latex/ensae/td_note_2025.py b/_latex/ensae/td_note_2025.py deleted file mode 100644 index dbe0bf2..0000000 --- a/_latex/ensae/td_note_2025.py +++ /dev/null @@ -1,10 +0,0 @@ -import numpy as np -import pandas - - -data = [[8, 7], [18, 18], [6, 8]] -df = pandas.DataFrame(data, columns=["schtroumph"] * 2) -df["index"] = ["schtroumph"] * 3 -df.set_index("index") -print(df) -print(df.to_latex(index=True)) From e965a7f278569061341ad563156f3713d68cd155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Dupr=C3=A9?= Date: Fri, 7 Nov 2025 00:42:38 +0100 Subject: [PATCH 3/8] fix --- .github/workflows/ci.yml | 8 +- _doc/practice/exams/td_note_2025.ipynb | 209 +++++++++++++++++++------ 2 files changed, 171 insertions(+), 46 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c46d592..6bc2446 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,4 +44,10 @@ jobs: - name: run tests run: | pip install pytest - PYTHONPATH=. UNITTEST_GOING=1 pytest --durations=10 _unittests --ignore-glob=**pygame*.py + PYTHONPATH=. UNITTEST_GOING=1 pytest --durations=10 _unittests --ignore-glob=**pygame*.py --ignore _unittests/ut_xrun_doc/test_documentation_examples.py --ignore _unittests/ut_xrun_doc/test_documentation_notebook.py + + - name: run tests examples + run: PYTHONPATH=. UNITTEST_GOING=1 python --durations=10 _unittests/ut_xrun_doc/test_documentation_examples.py + + - name: run tests notebooks + run: PYTHONPATH=. UNITTEST_GOING=1 python --durations=10 _unittests/ut_xrun_doc/test_documentation_notebooks.py diff --git a/_doc/practice/exams/td_note_2025.ipynb b/_doc/practice/exams/td_note_2025.ipynb index bd56c5e..b8e5319 100644 --- a/_doc/practice/exams/td_note_2025.ipynb +++ b/_doc/practice/exams/td_note_2025.ipynb @@ -15,12 +15,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Q1" + "## Q1 - Implémenter une fonction qui calcule la distance entre deux matrices" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "metadata": {}, "outputs": [ { @@ -29,7 +29,7 @@ "np.int64(45)" ] }, - "execution_count": 3, + "execution_count": 1, "metadata": {}, "output_type": "execute_result" } @@ -51,12 +51,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Q2" + "## Q2 - Implémenter une fonction qui retourne les p ermutations des n premiers entiers" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -88,7 +88,7 @@ " (3, 2, 1, 0)]" ] }, - "execution_count": 6, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -108,12 +108,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Q3, Q4" + "## Q3, Q4 - Implémenter une fonction qui p ermute les colonnes d'une matrice." ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -124,7 +124,7 @@ " [ 8, 7]])" ] }, - "execution_count": 23, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -145,12 +145,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Q5" + "## Q5 - Ecrire une fonctionne qui retourne les deux p ermutations ligne/colonne qui minimise la distance entre les deux matrices, en déduire la case qui a changé." ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -159,7 +159,7 @@ "((1, 0, 2), (1, 0))" ] }, - "execution_count": 24, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -184,7 +184,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Q6\n", + "## Q6 - Quel est le coût de cette fonction ?\n", "\n", "Si $i$ et $j$ sont les dimensions de deux tables, c'est $O((i!)(j!))$." ] @@ -193,12 +193,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Q7" + "## Q7 - C'est b eaucoup trop long.\n", + "\n", + "On prop ose que calculer chaque p ermutation séparément. On cherche donc la meilleure p ermutation qui minimise la distribution de la somme par ligne et par colonne entre les deux matrices. Ecrire une fonctionne qui implémente ce raisonnement.\n" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -207,7 +209,7 @@ "((0, 1), (1, 0, 2))" ] }, - "execution_count": 25, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -244,45 +246,162 @@ { "cell_type": "markdown", "metadata": {}, - "source": [] + "source": [ + "## Q8 - Mais c'est encore trop coûteux.\n", + "\n", + "On cherche la matrice M qui minimise AM =B\n", + "où A et B sont les sommes sur les colonnes où lignes des matrices de statistiques observées sur deux années." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Précisons d'abord ce qu'est une matrice de permutations $M$ : une matrice carrée dont les coefficients sont 0 ou 1. De plus, sur chaque ligne et chaque colonne, on ne trouve qu'un et un seul 1." + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A\n", + "[[0 1]\n", + " [2 3]\n", + " [4 5]]\n", + "M\n", + "[[0 1 0]\n", + " [0 0 1]\n", + " [1 0 0]]\n", + "M @ A\n", + "[[2 3]\n", + " [4 5]\n", + " [0 1]]\n" + ] + } + ], "source": [ - "import numpy as np\n", - "\n", + "M = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]])\n", + "A = np.arange(6).reshape((3, -1))\n", + "print(\"A\")\n", + "print(A)\n", + "print(\"M\")\n", + "print(M)\n", + "print(\"M @ A\")\n", + "print(M @ A)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$MA$ permute les lignes, $AM$ permute les colonnes. Donc trouver la matrice $M$ qui minimise $\\lVert AM - B \\rVert^2$ où $B$ obtenue avec une permutation des colonnes de la matrice $A$ permettrait de déterminer cette permutation. Ce n'est pas un système d'équations en bonne et due forme mais c'en est un." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 1.76711027 2.68346008]\n", + " [-1.00760456 -1.85931559]]\n", + "rang 2\n" + ] + } + ], + "source": [ + "M, _, rang, _ = np.linalg.lstsq(table1, table2)\n", + "print(M)\n", + "print(\"rang\", rang)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Le problème est qu'il y a plus d'inconnues que d'équations, d'où le rang faible (2). Avant de revenir à cette option. On part dans une autre direction. La plus grande des catégories de populations a beaucoup de chance d'être la plus grande l'année suivante. Donc la recherche du maximum dans les matrices A et B dévoile une partie de la matrice M. On applique cette idée aux sommes des lignes et des colonnes." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "((0, 1), (2, 0, 1))" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def optimise_vecteur_tri(vec1, vec2):\n", + " pos_vec1 = [(v, i) for i, v in enumerate(vec1)]\n", + " pos_vec1.sort()\n", + " return tuple(p[1] for p in pos_vec1)\n", "\n", - "coef = np.random.rand(5).reshape((-1, 1))\n", - "coef[:, 0] = 1\n", - "print(coef)\n", - "t1 = np.ones((5, 1)) @ np.array([[5, 1, 2, 3, 4]], dtype=np.float32)\n", - "t2 = np.ones((5, 1)) @ np.array([[2, 3, 1, 4, 5]], dtype=np.float32)\n", - "M = np.array(\n", - " [\n", - " [0, 0, 1, 0, 0],\n", - " [0, 0, 0, 1, 0],\n", - " [0, 1, 0, 0, 0],\n", - " [0, 0, 0, 0, 1],\n", - " [1, 0, 0, 0, 0],\n", - " ]\n", - ").T\n", "\n", - "# Il faut diminuer le nombre de solutions pour n'en garder qu'une.\n", - "for i in range(5):\n", - " t1[i, t1[i, :] == i] = i + 0.01\n", - " t2[i, t2[i, :] == i] = i + 0.01\n", + "def optimise_fast_tri(table1, table2):\n", + " return (\n", + " optimise_vecteur_tri(table1.sum(axis=0), table2.sum(axis=0)),\n", + " optimise_vecteur_tri(table1.sum(axis=1), table2.sum(axis=1)),\n", + " )\n", "\n", - "assert (t1 @ M - t2).max() < 1e-6\n", "\n", - "m = np.linalg.lstsq(t1, t2)\n", - "print(t1)\n", - "print(t2)\n", - "print(M)\n", - "print((m[0] * 100).astype(int) / 100)" + "optimise_fast_tri(table1, table2)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "On revient au problème d'optimisation : $\\lVert AM - B \\rVert^2$. Il faudrait pouvoir forcer les coefficients de la matrice à être 0 ou 1 en ajoutant une contrainte. On utilise pour cela fonction $f(x)=x(1-X)$ qui vaut 0 quand $x \\epsilon \\{0,1\\}$. On cherche donc $M$ qui minimise $\\lVert AM - B \\rVert^2 + \\lambda \\lVert M^2*(1-M)^2\\rVert$ où $*$ est une multiplication terme à terme. Mais résoudre ce problème n'est pas simple. On en restera là pour le moment." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q9 - Comment utiliser cette fonction p our implémenter une version plus rapide de la fonction à la question 5." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Le code de la question précédente répond à la question." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q10 - La troisième année, une colonne est coupée en deux : une catégorie est divisée en deux sous-catégorie. Que proposez-vous p our y remédier ?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "L'idée est assez simple, on choisit au hasard deux lignes de la seconde matrice et on les aggrège. On la fonction précédente pour en déduire les deux permutations les moins coûteuses puis on conserve le coût de cette permutation. On fait pour toutes les paires et on ne garde que la meilleure paire." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] } ], "metadata": { From f8e4372e7d5d7dd935e70af43e58ea9e5e532929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Dupr=C3=A9?= Date: Fri, 7 Nov 2025 10:25:18 +0100 Subject: [PATCH 4/8] fix --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bc2446..d3ecbf7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: PYTHONPATH=. UNITTEST_GOING=1 pytest --durations=10 _unittests --ignore-glob=**pygame*.py --ignore _unittests/ut_xrun_doc/test_documentation_examples.py --ignore _unittests/ut_xrun_doc/test_documentation_notebook.py - name: run tests examples - run: PYTHONPATH=. UNITTEST_GOING=1 python --durations=10 _unittests/ut_xrun_doc/test_documentation_examples.py + run: PYTHONPATH=. UNITTEST_GOING=1 python _unittests/ut_xrun_doc/test_documentation_examples.py - name: run tests notebooks - run: PYTHONPATH=. UNITTEST_GOING=1 python --durations=10 _unittests/ut_xrun_doc/test_documentation_notebooks.py + run: PYTHONPATH=. UNITTEST_GOING=1 python _unittests/ut_xrun_doc/test_documentation_notebooks.py From 6c96aae531cbecd9427c08355f065771ba3879de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Dupr=C3=A9?= Date: Fri, 7 Nov 2025 11:34:50 +0100 Subject: [PATCH 5/8] fix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3ecbf7..dbe41f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,4 +50,4 @@ jobs: run: PYTHONPATH=. UNITTEST_GOING=1 python _unittests/ut_xrun_doc/test_documentation_examples.py - name: run tests notebooks - run: PYTHONPATH=. UNITTEST_GOING=1 python _unittests/ut_xrun_doc/test_documentation_notebooks.py + run: PYTHONPATH=. UNITTEST_GOING=1 python _unittests/ut_xrun_doc/test_documentation_notebook.py From a12c345334ffd135b3bbd9d84f485cb0533d74d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Dupr=C3=A9?= Date: Fri, 7 Nov 2025 12:19:41 +0100 Subject: [PATCH 6/8] change ci --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dbe41f8..4b2604c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: PYTHONPATH=. UNITTEST_GOING=1 pytest --durations=10 _unittests --ignore-glob=**pygame*.py --ignore _unittests/ut_xrun_doc/test_documentation_examples.py --ignore _unittests/ut_xrun_doc/test_documentation_notebook.py - name: run tests examples - run: PYTHONPATH=. UNITTEST_GOING=1 python _unittests/ut_xrun_doc/test_documentation_examples.py + run: PYTHONPATH=. UNITTEST_GOING=1 pytest --durations=10 _unittests/ut_xrun_doc/test_documentation_examples.py -v - name: run tests notebooks - run: PYTHONPATH=. UNITTEST_GOING=1 python _unittests/ut_xrun_doc/test_documentation_notebook.py + run: PYTHONPATH=. UNITTEST_GOING=1 pytest --durations=10 _unittests/ut_xrun_doc/test_documentation_notebook.py -v From ae64c40c4a98ee6c21b08c2465640c78542e6a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Dupr=C3=A9?= Date: Fri, 7 Nov 2025 13:56:40 +0100 Subject: [PATCH 7/8] skip notebook --- .../test_documentation_notebook.py | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/_unittests/ut_xrun_doc/test_documentation_notebook.py b/_unittests/ut_xrun_doc/test_documentation_notebook.py index 3ecf8b3..fc64135 100644 --- a/_unittests/ut_xrun_doc/test_documentation_notebook.py +++ b/_unittests/ut_xrun_doc/test_documentation_notebook.py @@ -111,40 +111,38 @@ def add_test_methods_path(cls, fold, copy_folder=None): for name in found: if name.endswith(".ipynb"): fullname = os.path.join(fold, name) - if "interro_rapide_" in name or ( - sys.platform == "win32" - and ( - "protobuf" in name - or "td_note_2021" in name - or "nb_pandas" in name + reason = None + if not reason and ( + "interro_rapide_" in name + or ( + sys.platform == "win32" + and ( + "protobuf" in name + or "td_note_2021" in name + or "nb_pandas" in name + ) ) ): - - @unittest.skip("notebook with questions or issues with windows") - def _test_(self, fullname=fullname): - res = self.run_test(fullname, verbose=VERBOSE) - self.assertIn(res, (-1, 1)) - - elif "module_file_regex" in name and sys.platform != "win32": - - @unittest.skip("issues with linux") - def _test_(self, fullname=fullname): - res = self.run_test(fullname, verbose=VERBOSE) - self.assertIn(res, (-1, 1)) - - elif ( + reason = "protobuf on windows not working" + if not reason and "seance5_algo2" in name: + reason = "profiling not working on CI" + if ( + not reason + and "module_file_regex" in name + and sys.platform != "win32" + ): + reason = "regex not working on windows" + if not reason and ( "ml_a_tree_overfitting" in name and os.environ.get("CIRCLECI", "undefined") != "undefined" ): + reason = "tree_overfitting too long" + if not reason and "pretraitement_son" in name: + reason = "audio too long" - @unittest.skip("issues with circleci") - def _test_(self, fullname=fullname): - res = self.run_test(fullname, verbose=VERBOSE) - self.assertIn(res, (-1, 1)) - - elif "pretraitement_son" in name: + if reason: - @unittest.skip("audio not working") + @unittest.skip(reason) def _test_(self, fullname=fullname): res = self.run_test(fullname, verbose=VERBOSE) self.assertIn(res, (-1, 1)) From f799925981b76aa4b9d52be6841211917290cce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Dupr=C3=A9?= Date: Fri, 7 Nov 2025 14:40:39 +0100 Subject: [PATCH 8/8] fix --- _unittests/ut_xrun_doc/test_documentation_notebook.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/_unittests/ut_xrun_doc/test_documentation_notebook.py b/_unittests/ut_xrun_doc/test_documentation_notebook.py index fc64135..ae714f5 100644 --- a/_unittests/ut_xrun_doc/test_documentation_notebook.py +++ b/_unittests/ut_xrun_doc/test_documentation_notebook.py @@ -139,6 +139,8 @@ def add_test_methods_path(cls, fold, copy_folder=None): reason = "tree_overfitting too long" if not reason and "pretraitement_son" in name: reason = "audio too long" + if not reason and "ml_ml_features_model" in name: + reason = "http request denied" if reason: