{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Przygotowanie innowacyjnych materiałów szkoleniowych i dokumentacji wewnętrznych w obszarze IT\n",
"\n",
"## 4. Jupyter - kalkulator symboliczny: zadanie \"Pudełko Pad Thai\"\n",
"### Bartosz Naskręcki\n",
"\n",
"W tym pliku rozważymy zadanie z geometrii, którego rozwiązanie może być w całości wykonane z wykorzystaniem możliwości symbolicznych pakietu SymPy. Wszystkie obliczenia będą wykonane w rachunku symbolicznym, co powoduje, że nasze wyniki nie są przybliżeniami i wszelkie uzyskane formuły są udowodnione w sposób algebraiczny."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Zadanie:\n",
"\n",
"Rozważamy pudełko, którego ściany są pięciokątami foremnymi o długości boku 1. Podstawa pudełka jest kwadratem o boku 1. Zakładając, że ściany pudełka stykają się, oblicz jaka jest odległość pomiędzy górnymi wierzchołkami ścian pudełka leżących naprzeciw siebie."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Wykonujemy import z biblioteki SymPy. W przypadku komunikatu o błędzie importu, należy zainstalować bibliotekę w lokalnej instancji Pythona."
]
},
{
"cell_type": "code",
"execution_count": 93,
"metadata": {},
"outputs": [],
"source": [
"from sympy import *"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Biblioteka [SymPy](https://www.sympy.org) posiada wbudowane możliwości rachunków symbolicznych na zmiennych oraz obsługę funkcji matematycznych wraz z relacjami pomiędzy nimi. Konstruując rozwiązanie naszego zadania zapoznamy się z szeregiem standardowych funkcjonalności. Rozszerzone możliwości tej biblioteki są wbudowane w pakiet matematyczny [SageMath](https://www.sagemath.org)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Strategia rozwiązania zadania:\n",
"\n",
"Aby obliczyć pożądaną odległość spróbujemy \"wyobrazić\" sobie całą sytuację. Powstanie pudełka polega na obróceniu w przestrzeni 3D czterech pięciokątnych ścian o pewien kąt $\\alpha$. Wybór tego kąta jest jednoznacznie ustalony przez warunek stykania się ścian wzdłuż zewnętrznych krawędzi. Nasza strategia wygląda więc następująco:\n",
"\n",
"1. Generujemy mechanizm, który wyznacza symbolicznie współrzędne wierzchołków pięciokąta foremnego.\n",
"2. Pozycjonujemy cztery kopie takiego pięciokąta na płaszczyźnie.\n",
"3. Korzystając z transformacji obrotu o zadany kąt względem prostej obracamy wybrane ściany o pewien nieznany kąt $\\alpha$. \n",
"4. Formułujemy warunek stykania ścian (warunek ten jest równaniem).\n",
"5. Wyznaczamy kąt z równania (jako wyrażenie symboliczne).\n",
"6. Obliczamy odległość zadanych punktów korzystając z wbudowanych w SymPy tożsamości trygonometrycznych."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Macierz obrotu o zadany kąt\n",
"\n",
"Macierze w SymPy są inicjalizowane komendą Matrix. Jako argument funkcja ta pobiera listę składającą z się z list tej samej długości (reprezentujących wiersze)."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}1 & 2\\\\3 & 4\\end{matrix}\\right]$"
],
"text/plain": [
"Matrix([\n",
"[1, 2],\n",
"[3, 4]])"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"M1=Matrix([[1,2],[3,4]])\n",
"M1"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Na macierzach możemy wykonywać standardowe operacje dodawania i mnożenia, operację obliczania śladu, wyznacznika i wiele innych. Użytkownik może sprawdzić aktualną listę zaimplementowanych funkcji za pomocą nazwy obiektu i kropki oraz przez wciśnięcie tabulatora"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"M1. #<+tabulator>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Macierz obrotu wokół punktu $(0,0)$ o zadany kąt $t$ (przeciwnie do ruchu wskazówek zegara) jest macierzą postaci"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"def obrot(t):\n",
" m=Matrix([[cos(t),-sin(t)],[sin(t),cos(t)]])\n",
" return m"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}0 & -1\\\\1 & 0\\end{matrix}\\right]$"
],
"text/plain": [
"Matrix([\n",
"[0, -1],\n",
"[1, 0]])"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"obrot(pi/2) #obrót o 90 stopni przeciwnie do ruchu wskazówek zegara"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Działanie macierzy obrotu możemy przetestować na wektorach (macierzach o wymiarach $1\\times n$). Wykorzystamy zmienne symboliczne, aby wyrazić działanie w ogólnym przypadku"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"x,y=symbols('x,y') #za pomocą komendy symbols możemy inicjalizować listę abstrakcyjnych zmiennych symbolicznych"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Na zmiennych symbolicznych $x,y$ możemy wykonywać operacje podstawiania, elementarne operacje oraz wykorzystywać je jako argumenty w funkcjach, a także podstawiać pod nie wartości."
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle x + y$"
],
"text/plain": [
"x + y"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"#dodawanie zmiennych\n",
"x+y"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle x y$"
],
"text/plain": [
"x*y"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"#mnożenie zmiennych\n",
"x*y"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle x^{y}$"
],
"text/plain": [
"x**y"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"#potęgowanie zmiennych\n",
"x**y"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"UWAGA!\n",
"\n",
"W standardowym Pythonie symbol ^ oznacza operację dodawania bitowego XOR. Do oznaczenia potęgowania używamy symbolu **"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\sin{\\left(y \\right)} + \\cos{\\left(x \\right)}$"
],
"text/plain": [
"sin(y) + cos(x)"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"#funkcja z abstrakcyjnym argumentem\n",
"cos(x)+sin(y)"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\sin{\\left(y^{2} + 1 \\right)}$"
],
"text/plain": [
"sin(y**2 + 1)"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"#podstawienie argumentu\n",
"#w celu podstawienie wykorzystujemy strukturę słownika dict, której kluczami są podstawiane zmienne, a wartościami kluczy\n",
"#są wartości podstawiane\n",
"sin(x).subs({x:y**2+1})"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle 0.900968867902419$"
],
"text/plain": [
"0.900968867902419"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"#wartości symboliczne można przybliżać numerycznie\n",
"cos(pi/7).n()"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.9009688679024191"
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"#możemy konwertować wartości numeryczne do standardowego typu danych float\n",
"float(cos(pi/7))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Ciekawostka:\n",
"Niektóre stałe matematyczne, np. $\\pi$ są wbudowane w SymPy i traktowane jako pewne zmienne symboliczne, które posiadają szczególne własności, np. wartości $\\cos(\\pi/n)$ można podać algebraicznie i numerycznie. To bardzo wygodny sposób operowania podstawowymi stałymi matematyki."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Zbadajmy działanie operacji obrotu o kąt $t$ na wektorze o zmiennych symbolicznych $(x,y)$ "
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {},
"outputs": [],
"source": [
"t,x,y=symbols('t,x,y') #UWAGA: nie musimy ponownie deklarować zmiennych symbolicznych, robimy to tylko dla zachowania\n",
" #przejrzystości kodu"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}x\\\\y\\end{matrix}\\right]$"
],
"text/plain": [
"Matrix([\n",
"[x],\n",
"[y]])"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v=Matrix([[x],[y]])\n",
"v"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}\\cos{\\left(t \\right)} & - \\sin{\\left(t \\right)}\\\\\\sin{\\left(t \\right)} & \\cos{\\left(t \\right)}\\end{matrix}\\right]$"
],
"text/plain": [
"Matrix([\n",
"[cos(t), -sin(t)],\n",
"[sin(t), cos(t)]])"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"obrot_o_t=obrot(t)\n",
"obrot_o_t"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}x \\cos{\\left(t \\right)} - y \\sin{\\left(t \\right)}\\\\x \\sin{\\left(t \\right)} + y \\cos{\\left(t \\right)}\\end{matrix}\\right]$"
],
"text/plain": [
"Matrix([\n",
"[x*cos(t) - y*sin(t)],\n",
"[x*sin(t) + y*cos(t)]])"
]
},
"execution_count": 45,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"#obrót wektora (x,y) o kąt t\n",
"obrot_o_t*v"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Zadanie: jakie są współrzędne wektora $(1,3)$ po obrocie o kąt $\\pi/4$ ?"
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\displaystyle \\left[\\begin{matrix}- \\sqrt{2}\\\\2 \\sqrt{2}\\end{matrix}\\right]$"
],
"text/plain": [
"Matrix([\n",
"[ -sqrt(2)],\n",
"[2*sqrt(2)]])"
]
},
"execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(obrot_o_t*v).subs({x:1,y:3,t:pi/4})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Zadanie: Korzystając z wektorów i macierzy obrotów wyznacz współrzędne wierzchołków wielokata foremnego (mającego $n$ boków) na płaszczyźnie, którego boki mają długość 1\n",
"\n",
"Rozwiązanie metodą \"żółwia\":\n",
"\n",
"1. Wystartuj w punkcie $(0,0)$.\n",
"2. Przesuń się o jednostkę w prawo za pomocą wektora $k=(1,0)$. Jesteś w punkcie $P_0=(1,0)$.\n",
"3. Obróć głowę żółwia o kąt $2\\cdot \\pi/n$.\n",
"4. Przesuń żółwia o jednostkę \"do przodu\". Jesteś w punkcie $P_1=P_0+\\theta(t)(k)$\n",
"5. Powtarzaj punkty (3)-(5) $n-1$ razy.\n",
"\n",
"Zwróć listę wierzchołków: $P_0,\\ldots, P_{n-1}$.\n",
"\n",
"Uwaga: Operacja $\\theta(t)$ to obrót wektora o zadany kąt $t$. W naszym przypadku mnożymy wektor kolumnowy z lewej przez macierz obrotu o kąt $t$."
]
},
{
"cell_type": "code",
"execution_count": 127,
"metadata": {},
"outputs": [],
"source": [
"def NkatForemny(n):\n",
" p0=Matrix([[0],[0]])\n",
" k=Matrix([[1],[0]])\n",
" ob=obrot(2*pi/n)\n",
" nkat=[]\n",
" w=p0+k\n",
" for i in range(0,n):\n",
" nkat.append(w)\n",
" k=ob*k #obróć żółwia\n",
" w+=k #przesuń o jednostkę w nowym kierunku\n",
" return nkat"
]
},
{
"cell_type": "code",
"execution_count": 131,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"⎡ ⎡1/2⎤ ⎤\n",
"⎢⎡1⎤ ⎢ ⎥ ⎡0⎤⎥\n",
"⎢⎢ ⎥, ⎢√3 ⎥, ⎢ ⎥⎥\n",
"⎢⎣0⎦ ⎢── ⎥ ⎣0⎦⎥\n",
"⎣ ⎣2 ⎦ ⎦\n"
]
}
],
"source": [
"pretty_print(NkatForemny(3)) #współrzędne trójkąta foremnego"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Rysowanie z biblioteką matplotlib\n",
"\n",
"Do rysowania wykorzystamy uniwersalną bibliotekę matplotlib. Na potrzeby prawidłowego rysowania korzystamy z dodatkowej \"magicznej komendy\"\n",
"\n",
"%matplotlib inline"
]
},
{
"cell_type": "code",
"execution_count": 132,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Rysowanie wielokątów\n",
"\n",
"Wielokąt w bibliotece matplotlib możemy narysować korzystając z komendy `fill`. Do narysowania wielokąta przekazujemy osobno jako argumenty wartości współrzędnych $x$-owych i $y$-owych. Do rozbicia listy wierzcholków na współrzędne używamy komendy \n",
"\n",
"`zip(*lista_wierzcholkow)`\n",
"\n",
"Uwaga: aby figury nie nachodziły na siebie przesuwamy macierzowo zbiory wierzchołków o odpowiedni wektor $(3i,0)$ dla każdej $i$-tej figury."
]
},
{
"cell_type": "code",
"execution_count": 134,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAXIAAAD4CAYAAADxeG0DAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Il7ecAAAACXBIWXMAAAsTAAALEwEAmpwYAAAW+0lEQVR4nO3deXhV9Z3H8c83m0BYhRAkAeKISwF1FDpVsS5FWpj2qW21VsbaZWqxG9NtqsU+M512ZtTaqXSdto5dbGVsO9W2SoGKWqW2WiVuUIKAQCQQSELYwpL1O38k9MFIkpvcs9wfvl/Pw0Nycu/v+yXcfHLO7/zuOebuAgCEKy/tBgAA2SHIASBwBDkABI4gB4DAEeQAELiCNIqOGTPGKyoq0igNAMGqrKxscPeS7ttTCfKKigqtWrUqjdIAECwzqz7WdqZWACBwBDkABI4gB4DAEeQAEDiCHAACR5ADQOAIcgAIHEEOAIEjyAEgcAQ5AASOIAeAwBHkABA4ghwAAkeQA0DgCHIACBxBDgCBI8gBIHAEOQAEjiAHgMAR5AAQOIIcAAJHkANA4AhyAAgcQQ4AgSPIASBwBDkABI4gB4DAEeQAEDiCHAACR5ADQOAIcgAIHEEOAIEjyAEgcAQ5AASOIAeAwBHkABA4ghwAAkeQA0DgCHIACFxkQW5m+Wb2rJktiWpMAEDfotwj/6SkqgjHAwBkIJIgN7NySW+VdGcU4wEAMhfVHvnXJd0gqaOnB5jZfDNbZWar6uvrIyoLAMg6yM3sbZLq3L2yt8e5+x3uPsPdZ5SUlGRbFgDQJYo98pmS3m5mWyT9TNKbzOzuCMYFAGQg6yB394XuXu7uFZKulvSIu783684AABlhHTkABK4gysHc/VFJj0Y5JgCgd+yRA0DgCHIACBxBDgCBI8gBIHAEOQAEjiAHgMAR5AAQOIIcAAJHkANA4AhyAAgcQQ4AgSPIASBwBDkABI4gB4DAEeQAEDiCHAACR5ADQOAIcgAIHEEOAIEjyAEgcAQ5AASOIAeAwBHkABA4ghwAAkeQA0DgCHIACBxBDgCBI8gBIHAEOQAEjiAHgMAR5AAQOIIcAAJHkANA4AhyAAgcQQ7gNa+ttV1bqxrV0eFptzIgBWk3AABpaTncpjWPbdNzD2/VoX0tGl4yWOe+eaLOOP8k5ReEs5+bdZCb2QRJP5FUKskl3eHu38h2XACIy+EDrXrh9zV64fdb1Xyg7a/b99Uf0qOLX9SqpVv0t7MnauqF41VQlJ9ip5mJYo+8TdJn3f0ZMxsmqdLMVrj72gjGBoDIHNzXouceellrVm5T6+H2Hh/XtLtZj/9igyqXbdHZsybozEvKVTQodycwsu7M3Wsl1XZ9vN/MqiSVSSLIAeSE/Y2H9eyKl1X1+Ha1tXZk/LxD+1v15K836dkHX9aZl5br7DdN0KDiwhg7HRhzj25y38wqJK2UNM3d93X72nxJ8yVp4sSJ06urqyOrCwDHsrf+oCqXV+vFP+9QR1v2WVd4Qr6mXlSmc2ZP1JDhRRF02D9mVunuM161PaogN7Ohkh6T9J/ufl9vj50xY4avWrUqkroA0N2u7U2qXFatjZV18hhWouQX5mnKBSfpnLdM0rATB0U+fk96CvJIJn3MrFDSvZIW9xXiABCXuup9qlxWrU3P13cuvYhJe2uHVj+2TX95fLtOf8M4nfuWSRpZOiS+gn2IYtWKSfqBpCp3vz37lgCgf7Zv3KPKpVv08trGROt2tLuq/lSrdU/u0ORzSzR9boVGlw1NtAcpmj3ymZKulbTazJ7r2naTuy+NYGwA6NHLa3epclm1tm/Yk2of3uHasKpOGyrrdPJZYzR9boVKK4YnVj+KVSuPS7IIegEQgdbaWnlzc+Tj5g0bpoLRoyMfd6AevHONNqyqS7uNV3Jp8/MN2vx8g6bPnaTzLj8lkbK5uzASQL81b9qkTZe/Q2ptjXzsgpISnbJ8mfKKiyMfu7862ju0efWutNvo1ebnGxIL8nDegwqgTztvuTWWEJektvp6NXzv+7GM3V8NNU1qa+75DT25oLH2gJoPxvN/0R1BDhwnmh57TAf+8IdYazTedZdatm6NtUYmdmzam3YLfXNpx6Z9fT8uAgQ5cBzw1lbtvPUr8ddpaVHdbbfFXqcvtS8FEOSSal/ak0gdghyvKWsa1mjhHxaq/mB92q1EqvHuxWrZvDmRWvtXPKQDTz6ZSK2e7AgkyJM6ciDI8Zrg7vrhmh/q2mXXasmmJbri/iu0smZl2m1Foq2xUQ3//d+J1tx58y3y9nTmqJt2H1bT7uhX5cRh55b96mjP/NouA0WQ47jXcKhB16+4XosqF6mto/OSpbubd+vjD39ctz51q1raW1LuMDv1i76ujv37E63ZvH69dv/854nWPCKUaRVJamtuV0NNU+x1CHIc1x7f9riuuP8KPVH7xDG/vrhqsa5Zeo02701mWiJqh6uqtOfee1Op3fDNb6l9b/KhGsq0yhFJTK8Q5Dgutba36ranb9PHHvqYGg/3/rbtdY3r9J4l79F9G8K7TNDOm2+ROuI/dD+W9j17VP/t7yReN6Q9cimZfglyHHeq91XrmqXX6KdrfyrP8MpJh9oO6Yt/+qI+99jntL8l2WmKgdq3fLkOPv10qj3svuceNb/0UmL1WpvbtSuBqYooJXEEQZDjuPLrjb/WVQ9cparGqgE9f/mW5Xr3A+/Wc3XPRdtYxDqam1V321fTbkNqa+s8KkjIzi37grtBctPuZu1vPBxrDYIcx4WmlibduPJG/csf/0UH2w5mNda2pm364PIP6o4X7lCHpzNt0ZddP/iBWrdvT7sNSdKBP/5R+x/5fSK1QpsfPyLueXKCHMFbXb9a737g3Vq6OboLbrZ5m7717Lf04Qc/rLqDuXVhptYdO7Trf+5Mu41XqPvKV+Qt8a/+CW1+/Ii4+ybIESx3152r79T7lr9PNU01sdR4asdTuuL+K/To1kdjGX8g6r52u/zQobTbeIWW6mo1/vTuWGu4u3ZuDjPI4z6SIMgRpPqD9Zq/Yr6+8cw3/ro2PC57mvdowSMLdPOfb059zfnBZ5/VviVLUu2hJw3f/a7adsV3RcLOi1DF+38dl101TWqN8SJfBDmCs7Jmpa584Eo9WZvs28TvWXeP5v12njbt2ZRo3SPcvfPEYoQ3TI9SR1OT6hYtim38UOfHJamjw7VzS3wX0OJ65CH7txExjp17PzSt7a26vfJ2La5anPGywqit371eV//2at3w+ht05WlXJlp7769+rcOrVydas7/23vcrjZo3T4OnTo187CCueNiLHS/tVfnpo2IZmz1yBOObz35Td1fdnVqIH3Go7ZC+9MSX9HD1w4nVbG86oLpFAdwSt6MjtuWIoZ7oPCLO/glyBGN66fS0W/irfMvXOaXnJFZv1/e/r/b6hsTqZeNQZaX2LY32lr2H9rdob11uneDtr52b98pjmhY7boO8vcP1m+e2pd0GInTB+AtUXJj+bcakzl8qJw46MbF6h3J8SqW7Qy9E22/oe+OS1HywTY21B2IZ+7gN8v996mXdeO8Lqtmd3ZtDkDuK8ot0UflFabchSbps0mWJ1hv7uX+W8sL4cc0fMUJjPnJ9pGOGPj9+RFwnbMN4ZfTT3oOtWrRivQ63duiWpevSbgcRmj1pdtotyGSaNXFWojUHT52qEe96Z6I1B2rMggXKHzky0jHHThoe6XhpyCswjS4bGs/YsYyaskUPrVfjgc71vr9dXas/b8rtu20jcxeWXajBBYNT7eHskrM1dsjYxOuO/fSnlTc0niCIygmnnqpR866OfNzJ08fqnNkTIx83SRe95zSN+5t4Vpodd0G+sW6/7n6y+hXbvvTA2uAutINjG1wwWDPHz0y1h6SnVY4oGD1aYz760VRqZ6r0poWy/PxYxj7vnadowuviWb4XtykXjtfUN5bFNv5xF+RfXlKltm6hvbZ2n36+Kv07fyMaaQXpEWlO75z4vmtVNGlSavV7M3TWLBWff35s4+flmd583TQNHzMothpxKD15uC66+rRYaxxXQf5w1U6tXH/sm+p+7cEXte9wa8IdIQ6XTLhERXlFqdSeOnqqxg8dn0ptSbLCQo298cbU6vfECgtVeuMNsdcZVFyouR85UwVFYUTXkOFFmnv9mcoviLffML4bGWht79B//Lbna1A3NLXomw9tSLAjxKW4sFjnj49vz683aR8NSNKwN12q4gsvTLuNVzjx/e9T0cRk5rDHlA/TpdeekUitbOTlm+bMn6bikSfEXyv2Cgn50R83a3ND72s073piizbVh3V3ERxbWoGaC6tmJKl04eelgty4wkZ+yRiN/kiyc/envX6czr5sQqI1++uNV52qkyaPTKTWcRHkDU3N+tbDG/t8XGu797rXjnBcOuFSFViyQXbaqNM0aXhuzE+fcMopGjVvXtptSJLGfurTyh+a/Bu1LnjXZJWfkZsnP1838yRNu7g8sXrHRZB/dfmL2t+c2eUtH1lXp0dfzK0bBaD/RpwwQq8f9/pEa+bCtMrRShZ8Qvmj0g2yQdOmpba+vfPk51QNOzG3Tn6OrRiui68+PdGawQf5mm179X+V/VuR8u9L1qqtPTdv4YXMJR2ssyfmxrTKEfnDh6vknxak2kPpTTfJzFKrP3hoUefJz8LciLLBw4s09/ppyk+4n9z412fhyw+sVX+XiL9Uf0A/eaK67wcip82aOEt5lsxLuGJ4hSaPmpxIrf4YedVVOuG0eJe29WT4W9+qIecmd+GwnpRMHKZL3pv+yc+8fNOcD0/T0FHJHyEEHeQPPL9dT21pHNBzv37Uuz8RptGDR+vcsecmUitXTnJ2Z/n5Kr1pYfJ1Bw/uvP5Ljjj9DeN01qXJzUkfy8wrJ2v8qSNTqR1skB9ubdetywZ+HZV9h9v0tQdfjLAjpCGp6ZVcDXJJKj7vPA2bnew00+jrPqTCceMSrdmXmVdOVtlpI1OpfcZ543TWpemtogk2yL/32Evatie76xP/7OmtqqqN7/ZLiN9lEy+TKd452rKhZXrd6NfFWiNbY2+8UVaUzJukCseP1+gPfSiRWv2Rl5+nt3x4moaOin/d9tHGThqmi69J9uRmd5EEuZnNMbMXzWyjmX0+ijF7U7v3kL7/WPb3TWzvcH35gbURdIS0lBaX6sySM2Otkct740cUlZfrxA98IJFaYz/3z8oblFsrRY4YPKzz5GdSJxsHDyvUnOvPVEFhPNeXyVTW/1ozy5f0HUlzJU2RNM/MpmQ7bm9uWbpOh1qjuSP1E5t2afma2kjGQjriXk2Sa8sOezLm+vkqKCmJtcbgGdM1fO7cWGtka+yk4brkH+LfQ87LM73lumk5sfwxil9bfydpo7tvcvcWST+TdHkE4x7Tqi2Nuv/57ZGO+Z9Lq9TcFs0vBiRvdkV8QV46pFRnjTkrtvGjlFdcrJLPfCbGAnkad9NN8Y0foTPOP0lnXhzf1QYl6YIrJqssppsp91cUb40rk3T0Qu4aSW+IYNxj2rLroK678OTIx92ws0nTymK8Kz1iUza0TAvOWaC9zdHffWXK6CmprpPurxHvuFytNTXqOBD9LcWKKio0aEqsB9uRmnnVqTqhuFCtLdHvpBWPOEFnz8qdSwRYtjcDNbMrJc1x9+u6Pr9W0hvc/RPdHjdf0nxJmjhx4vTqatZxA0B/mFmlu8/ovj2KqZVtko7+1VTete0V3P0Od5/h7jNKYp7HA4DXkiiC/GlJp5rZyWZWJOlqSfdHMC4AIANZz5G7e5uZfULS7yTlS/qhu/8l684AABmJ5Dqg7r5U0tIoxgIA9E+w7+wEAHQiyAEgcAQ5AASOIAeAwBHkABA4ghwAAkeQA0DgCHIACBxBDgCBI8gBIHAEOQAEjiAHgMAR5AAQOIIcAAJHkANA4AhyAAgcQQ4AgSPIASBwBDkABI4gB4DAEeQAEDiCHAACR5ADQOAIcgAIHEEOAIEjyAEgcAQ5AASOIAeAwBHkABA4ghwAAkeQA0DgCHIACBxBDgCBI8gBIHAEOQAEjiAHgMBlFeRm9lUzW2dmL5jZr8xsZER9AQAylO0e+QpJ09z9LEnrJS3MviUAQH9kFeTu/qC7t3V9+qSk8uxbAgD0R5Rz5P8oaVlPXzSz+Wa2ysxW1dfXR1gWAF7bCvp6gJk9JGncMb70BXf/TddjviCpTdLinsZx9zsk3SFJM2bM8AF1CwB4lT6D3N0v6+3rZvYBSW+TNMvdCWgASFifQd4bM5sj6QZJF7v7wWhaAgD0R7Zz5N+WNEzSCjN7zsy+F0FPAIB+yGqP3N0nR9UIAGBgeGcnAASOIAeAwBHkABA4ghwAAkeQA0DgCHIACBxBDgCBI8gBIHAEOQAEjiAHgMAR5AAQOIIcAAJHkANA4AhyAAgcQQ4AgSPIASBwBDkABI4gB4DAEeQAEDiCHAACR5ADQOAIcgAIHEEOAIEjyAEgcAQ5AASOIAeAwBHkABA4ghwAAkeQA0DgCHIACBxBDgCBI8gBIHAEOQAEjiAHgMCZuydf1KxeUvUAnz5GUkOE7SSBnuMXWr8SPSchtH6l3nue5O4l3TemEuTZMLNV7j4j7T76g57jF1q/Ej0nIbR+pYH1zNQKAASOIAeAwIUY5Hek3cAA0HP8QutXouckhNavNICeg5sjBwC8Uoh75ACAoxDkABC4oILczOaY2YtmttHMPp92P70xswlm9nszW2tmfzGzT6bdU6bMLN/MnjWzJWn3kgkzG2lmvzSzdWZWZWbnp91Tb8zs012viTVmdo+ZDUq7p+7M7IdmVmdma47adqKZrTCzDV1/j0qzx+566PmrXa+LF8zsV2Y2MsUWX+VYPR/1tc+amZvZmL7GCSbIzSxf0nckzZU0RdI8M5uSble9apP0WXefIuk8SR/P8X6P9klJVWk30Q/fkLTc3c+QdLZyuHczK5P0T5JmuPs0SfmSrk63q2P6saQ53bZ9XtLD7n6qpIe7Ps8lP9are14haZq7nyVpvaSFSTfVhx/r1T3LzCZIerOklzMZJJggl/R3kja6+yZ3b5H0M0mXp9xTj9y91t2f6fp4vzrDpSzdrvpmZuWS3irpzrR7yYSZjZB0kaQfSJK7t7j7nlSb6luBpMFmViBpiKTtKffzKu6+UlJjt82XS7qr6+O7JL0jyZ76cqye3f1Bd2/r+vRJSeWJN9aLHr7PkrRI0g2SMlqNElKQl0naetTnNQogGCXJzCoknSPpzym3komvq/MF1JFyH5k6WVK9pB91TQfdaWbFaTfVE3ffJum/1LmnVStpr7s/mG5XGSt199quj3dIKk2zmQH4R0nL0m6iL2Z2uaRt7v58ps8JKciDZGZDJd0r6VPuvi/tfnpjZm+TVOfulWn30g8Fks6V9F13P0fSAeXeIf9fdc0rX67OX0DjJRWb2XvT7ar/vHPdcjBrl83sC+qc7lycdi+9MbMhkm6S9K/9eV5IQb5N0oSjPi/v2pazzKxQnSG+2N3vS7ufDMyU9HYz26LOqas3mdnd6bbUpxpJNe5+5Gjnl+oM9lx1maTN7l7v7q2S7pN0Qco9ZWqnmZ0kSV1/16XcT0bM7AOS3ibpGs/9N86cos5f8s93/RyWS3rGzMb19qSQgvxpSaea2clmVqTOE0T3p9xTj8zM1DlvW+Xut6fdTybcfaG7l7t7hTq/v4+4e07vLbr7Dklbzez0rk2zJK1NsaW+vCzpPDMb0vUamaUcPjnbzf2S3t/18fsl/SbFXjJiZnPUOVX4dnc/mHY/fXH31e4+1t0run4OaySd2/U671EwQd51wuITkn6nzhf+L9z9L+l21auZkq5V517tc11//j7tpo5TCyQtNrMXJP2tpJvTbadnXUcOv5T0jKTV6vwZzLm3kZvZPZKekHS6mdWY2Yck3SpptpltUOeRxa1p9thdDz1/W9IwSSu6fga/l2qT3fTQc//Hyf0jDQBAb4LZIwcAHBtBDgCBI8gBIHAEOQAEjiAHgMAR5AAQOIIcAAL3/58vegIUPAwAAAAAAElFTkSuQmCC\n",
"text/plain": [
"