{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "3378c30f",
   "metadata": {},
   "source": [
    "# Introduction to RNNs: learning the alphabet\n",
    "\n",
    "**Note**: this notebook is part of the complete lesson 34 repository [`gperdrizet/RNNs`](https://github.com/gperdrizet/RNNs) and is intended to be run in the environment set-up by that repository.\n",
    "\n",
    "A simple example to understand demonstration of how Recurrent Neural Networks learn sequential patterns.\n",
    "\n",
    "## Key concepts\n",
    "\n",
    "- **Sequence learning**: RNNs process input one step at a time, maintaining a \"memory\" (hidden state) of what they've seen.\n",
    "- **Next-character prediction**: Given a sequence like `['a', 'b', 'c']`, predict the next character (`'d'`).\n",
    "- **Hidden state**: The internal memory that carries information from previous timesteps (or indexes) in a sequence.\n",
    "\n",
    "## How the RNN processes a sequence\n",
    "\n",
    "Given input `['a', 'b', 'c']`, the RNN predicts `'d'`:\n",
    "\n",
    "```text\n",
    "\n",
    "                     ┌─◀──────┐ 'h₁', then 'h₂'\n",
    "                     │        ▲ \n",
    "                     ▼        |\n",
    "                    ┌──────────┐\n",
    "                    |   RNN    |         ┌───────┐\n",
    "'a', then 'b', ────▶| (hidden  |──'h₃'──▶│ Dense │───▶ 'c'\n",
    "   then 'c'         | state h) |         └───────┘ \n",
    "                    └──────────┘\n",
    "\n",
    "\n",
    "h₁ = RNN('a', h₀)        # h₁ \"knows\" about 'a'\n",
    "h₂ = RNN('b', h₁)        # h₂ \"knows\" about 'a', 'b'  \n",
    "h₃ = RNN('c', h₂)        # h₃ \"knows\" about 'a', 'b', 'c'\n",
    "output = Dense(h₃) → 'd' # Final hidden state predicts next char\n",
    "```\n",
    "\n",
    "The same RNN weights are used at each step within a sequence — only the hidden state changes.\n",
    "\n",
    "## Setup\n",
    "\n",
    "### Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "8b23c200",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "\n",
    "from tensorflow.keras.models import Sequential\n",
    "from tensorflow.keras.layers import SimpleRNN, Dense\n",
    "from tensorflow.keras.utils import to_categorical"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b40f911a",
   "metadata": {},
   "source": [
    "### Configuration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "80f5d901",
   "metadata": {},
   "outputs": [],
   "source": [
    "epochs=300\n",
    "batch_size=4\n",
    "\n",
    "# Force CPU only\n",
    "tf.config.set_visible_devices([], 'GPU')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79b7f84e",
   "metadata": {},
   "source": [
    "## 1. Data preparation\n",
    "\n",
    "### 1.1. Create the alphabet dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "2168c2bd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vocabulary: abcdefghijklmnopqrstuvwxyz\n",
      "\n",
      "Mappings:\n",
      "\n",
      " a -> 0\n",
      " b -> 1\n",
      " c -> 2\n",
      " d -> 3\n",
      " e -> 4\n",
      " f -> 5\n",
      " g -> 6\n",
      " h -> 7\n",
      " i -> 8\n",
      " j -> 9\n",
      " k -> 10\n",
      " l -> 11\n",
      " m -> 12\n",
      " n -> 13\n",
      " o -> 14\n",
      " p -> 15\n",
      " q -> 16\n",
      " r -> 17\n",
      " s -> 18\n",
      " t -> 19\n",
      " u -> 20\n",
      " v -> 21\n",
      " w -> 22\n",
      " x -> 23\n",
      " y -> 24\n",
      " z -> 25\n"
     ]
    }
   ],
   "source": [
    "# Create alphabet mapping\n",
    "alphabet = 'abcdefghijklmnopqrstuvwxyz'\n",
    "char_to_idx = {c: i for i, c in enumerate(alphabet)}\n",
    "idx_to_char = {i: c for i, c in enumerate(alphabet)}\n",
    "vocab_size = len(alphabet)\n",
    "\n",
    "print(f'Vocabulary: {alphabet}')\n",
    "\n",
    "print('\\nMappings:\\n')\n",
    "\n",
    "for key, val in char_to_idx.items():\n",
    "    print(f' {key} -> {val}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "381f58f6",
   "metadata": {},
   "source": [
    "### 1.2. Training 'freatures' & labels"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "20df9dca",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Input sequence  -> label\n",
      "('a', 'b', 'c') -> d\n",
      "('b', 'c', 'd') -> e\n",
      "('c', 'd', 'e') -> f\n",
      "('d', 'e', 'f') -> g\n",
      "('e', 'f', 'g') -> h\n",
      "('f', 'g', 'h') -> i\n",
      "('g', 'h', 'i') -> j\n",
      "('h', 'i', 'j') -> k\n",
      "('i', 'j', 'k') -> l\n",
      "('j', 'k', 'l') -> m\n",
      "('k', 'l', 'm') -> n\n",
      "('l', 'm', 'n') -> o\n",
      "('m', 'n', 'o') -> p\n",
      "('n', 'o', 'p') -> q\n",
      "('o', 'p', 'q') -> r\n",
      "('p', 'q', 'r') -> s\n",
      "('q', 'r', 's') -> t\n",
      "('r', 's', 't') -> u\n",
      "('s', 't', 'u') -> v\n",
      "('t', 'u', 'v') -> w\n",
      "('u', 'v', 'w') -> x\n",
      "('v', 'w', 'x') -> y\n",
      "('w', 'x', 'y') -> z\n",
      "('x', 'y', 'z') -> a\n",
      "X shape: (24, 3), y shape: (24,)\n"
     ]
    }
   ],
   "source": [
    "seq_length = 3\n",
    "\n",
    "y = alphabet[seq_length:] + 'a'  # Wrap around: 'xyz' -> 'a'\n",
    "x = list(zip(*[alphabet[i:] for i in range(seq_length)]))\n",
    "\n",
    "print('\\nInput sequence  -> label')\n",
    "\n",
    "for features, label in zip(x, y):\n",
    "    print(f'{features} -> {label}')\n",
    "\n",
    "print(f'X shape: {np.array(x).shape}, y shape: {np.array(list(y)).shape}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84e9b9c8",
   "metadata": {},
   "source": [
    "### 1.3. One-hot encoding"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "61098b28",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "First sequence (one-hot):\n",
      " 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0...\n",
      " 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0...\n",
      " 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0...\n",
      "\n",
      "First label (one-hot):\n",
      " 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0...\n",
      "\n",
      "X shape (one-hot): (24, 3, 26)\n",
      "y shape (one-hot): (24, 26)\n"
     ]
    }
   ],
   "source": [
    "X_indices = np.array([[char_to_idx[c] for c in seq] for seq in x])\n",
    "y_indices = np.array([char_to_idx[c] for c in y])\n",
    "\n",
    "X_onehot = to_categorical(X_indices, num_classes=vocab_size)\n",
    "y_onehot = to_categorical(y_indices, num_classes=vocab_size)\n",
    "\n",
    "print('First sequence (one-hot):')\n",
    "\n",
    "for ele in X_onehot[0][:, :10]:\n",
    "    print(f' {(\", \").join(map(str, ele))}...')\n",
    "\n",
    "print('\\nFirst label (one-hot):')\n",
    "print(f' {(\", \").join(map(str, y_onehot[0][:10]))}...')\n",
    "\n",
    "print(f'\\nX shape (one-hot): {X_onehot.shape}')\n",
    "print(f'y shape (one-hot): {y_onehot.shape}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1d2a5f3c",
   "metadata": {},
   "source": [
    "## 2. Recurrent model\n",
    "\n",
    "A simple architecture:\n",
    "1. **Input**: One-hot encoded characters (26-dimensional vectors)\n",
    "2. **SimpleRNN**: Process the sequence, output final hidden state  \n",
    "3. **Dense**: Predict the next character\n",
    "\n",
    "With one-hot encoding, each input character is a vector of 26 zeros with a single 1 at the character's index position.\n",
    "\n",
    "### 2.1. Define RNN"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "81b8060e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential\"\n",
      "_________________________________________________________________\n",
      " Layer (type)                Output Shape              Param #   \n",
      "=================================================================\n",
      " simple_rnn (SimpleRNN)      (None, 8)                 280       \n",
      "                                                                 \n",
      " dense (Dense)               (None, 26)                234       \n",
      "                                                                 \n",
      "=================================================================\n",
      "Total params: 514 (2.01 KB)\n",
      "Trainable params: 514 (2.01 KB)\n",
      "Non-trainable params: 0 (0.00 Byte)\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "# Model parameters\n",
    "hidden_dim = 8 # RNN hidden state size\n",
    "\n",
    "model = Sequential([\n",
    "    SimpleRNN(hidden_dim, input_shape=(seq_length, vocab_size)),\n",
    "    Dense(vocab_size, activation='softmax')\n",
    "])\n",
    "\n",
    "model.compile(\n",
    "    optimizer='adam',\n",
    "    loss='categorical_crossentropy',\n",
    "    metrics=['accuracy']\n",
    ")\n",
    "\n",
    "model.summary()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de0fba5a",
   "metadata": {},
   "source": [
    "### 2.2. Train RNN"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "cb5d1af5",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n",
      "I0000 00:00:1771281877.304733  645467 service.cc:145] XLA service 0x780ed4008aa0 initialized for platform Host (this does not guarantee that XLA will be used). Devices:\n",
      "I0000 00:00:1771281877.304775  645467 service.cc:153]   StreamExecutor device (0): Host, Default Version\n",
      "I0000 00:00:1771281877.374008  645476 device_compiler.h:188] Compiled cluster using XLA!  This line is logged at most once for the lifetime of the process.\n"
     ]
    }
   ],
   "source": [
    "history = model.fit(\n",
    "    X_onehot, y_onehot,\n",
    "    epochs=epochs,\n",
    "    batch_size=batch_size,\n",
    "    verbose=0\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "46732ca6",
   "metadata": {},
   "source": [
    "### 2.3. Learning curves"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "8e2fd47d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAk0AAAEiCAYAAADksOZKAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAV1BJREFUeJzt3Xl4TNcbB/DvTJbJvu9EEkUWIfaIXYVYSkOrtoqqUlvR6BZraSvaWrpQWor2V0ppBbVGCEXsYt+CLMgiQiaLrHN/f6SZdpqESSRzJ5nv53nu85gz595572WOd8499xyJIAgCiIiIiOippGIHQERERFQbMGkiIiIiUgOTJiIiIiI1MGkiIiIiUgOTJiIiIiI1MGkiIiIiUgOTJiIiIiI1MGkiIiIiUgOTJiIiIiI1MGmiavPGG2/A3d29Svt+/PHHkEgk1RuQmp4nbiJ6frW17SDdw6RJB0gkErW26OhosUMlIi3CtoNIlYRrz9V9v/zyi8rrn3/+GZGRkfjf//6nUt6zZ084OjpW+XMKCwuhUCggk8kqvW9RURGKiopgZGRU5c+vqjfeeAPR0dGIj4/X+GcTaTO2HUSqmDTpoMmTJ2P58uV41l99bm4uTExMNBSVeJg0EamHbUftJQgC8vLyYGxsLHYotRpvzxEAoFu3bvD19cWZM2fQpUsXmJiYYMaMGQCAbdu2oV+/fnBxcYFMJsMLL7yATz75BMXFxSrH+O+4hPj4eEgkEixatAg//PADXnjhBchkMrRt2xanTp1S2be8cQkSiQSTJ09GREQEfH19IZPJ0LRpU+zZs6dM/NHR0WjTpg2MjIzwwgsv4Pvvv3+usQ45OTmYPn06XF1dIZPJ4OnpiUWLFpX5zyIyMhKdOnWClZUVzMzM4Onpqbxupb799ls0bdoUJiYmsLa2Rps2bbBhw4YqxUWkbXS57fjrr78wePBgNGjQADKZDK6urnj33Xfx5MmTMnWvXbuG1157Dfb29jA2NoanpydmzpypUufevXsYM2aM8np5eHhgwoQJKCgoqPBcAWDdunWQSCQqP/zc3d3x0ksvYe/evWjTpg2MjY3x/fffAwDWrl2LF198EQ4ODpDJZPDx8cGKFSvKPcfdu3eja9euMDc3h4WFBdq2batsv+bOnQsDAwM8ePCgzH7jxo2DlZUV8vLynnkdaxN9sQMg7fHw4UP06dMHQ4cOxeuvv67sbl+3bh3MzMwQGhoKMzMzHDhwAHPmzIFcLseXX375zONu2LABWVlZePvttyGRSPDFF19g0KBBuH37NgwMDJ6675EjR/DHH39g4sSJMDc3xzfffINXXnkFiYmJsLW1BQCcO3cOvXv3hrOzM+bNm4fi4mLMnz8f9vb2VboOgiBgwIABOHjwIMaMGYMWLVpg7969eP/993Hv3j0sXboUAHD58mW89NJLaN68OebPnw+ZTIa4uDgcPXpUeaxVq1ZhypQpePXVVzF16lTk5eXhwoULOHHiBIYPH16l+Ii0ja62HZs3b0Zubi4mTJgAW1tbnDx5Et9++y3u3r2LzZs3K+tduHABnTt3hoGBAcaNGwd3d3fcunULO3bswGeffQYAuH//Ptq1a4fHjx9j3Lhx8PLywr1797Blyxbk5ubC0NBQrZj+7fr16xg2bBjefvttjB07Fp6engCAFStWoGnTphgwYAD09fWxY8cOTJw4EQqFApMmTVLuv27dOrz55pto2rQpwsLCYGVlhXPnzmHPnj0YPnw4Ro4cifnz52PTpk2YPHmycr+CggJs2bIFr7zySt27bSqQzpk0aZLw37/6rl27CgCElStXlqmfm5tbpuztt98WTExMhLy8PGXZqFGjBDc3N+XrO3fuCAAEW1tbISMjQ1m+bds2AYCwY8cOZdncuXPLxARAMDQ0FOLi4pRl58+fFwAI3377rbKsf//+gomJiXDv3j1l2c2bNwV9ff0yxyzPf+OOiIgQAAiffvqpSr1XX31VkEgkyniWLl0qABAePHhQ4bFffvlloWnTps+Mgag2YNvx7PMLDw8XJBKJkJCQoCzr0qWLYG5urlImCIKgUCiUfw4JCRGkUqlw6tSpMscsrVfeuQqCIKxdu1YAINy5c0dZ5ubmJgAQ9uzZo1bcQUFBQsOGDZWvHz9+LJibmwv+/v7CkydPKow7ICBA8Pf3V3n/jz/+EAAIBw8eLPM5tR1vz5GSTCbD6NGjy5T/+x54VlYW0tPT0blzZ+Tm5uLatWvPPO6QIUNgbW2tfN25c2cAwO3bt5+5b2BgIF544QXl6+bNm8PCwkK5b3FxMfbv34/g4GC4uLgo6zVq1Ah9+vR55vHLs2vXLujp6WHKlCkq5dOnT4cgCNi9ezcAwMrKCkDJLQiFQlHusaysrHD37t0ytxSI6hJdbTv+fX45OTlIT09Hhw4dIAgCzp07BwB48OABDh8+jDfffBMNGjRQ2b/0VptCoUBERAT69++PNm3alPmcqg4z8PDwQFBQ0FPjzszMRHp6Orp27Yrbt28jMzMTQMnQg6ysLHz00Udleov+HU9ISAhOnDiBW7duKcvWr18PV1dXdO3atUpxazMmTaRUr169cruAL1++jIEDB8LS0hIWFhawt7fH66+/DgDKL9jT/LehKG0EHz16VOl9S/cv3TctLQ1PnjxBo0aNytQrr0wdCQkJcHFxgbm5uUq5t7e38n2gpEHv2LEj3nrrLTg6OmLo0KH47bffVBKoDz/8EGZmZmjXrh0aN26MSZMmqdy+I6oLdLXtSExMxBtvvAEbGxuYmZnB3t5emSiUnl9pkubr61vhcR48eAC5XP7UOlXh4eFRbvnRo0cRGBgIU1NTWFlZwd7eXjkOrTTu0iToWTENGTIEMpkM69evV+7/559/YsSIEXVy/iwmTaRU3lMVjx8/RteuXXH+/HnMnz8fO3bsQGRkJD7//HMAqLCH5d/09PTKLRfUeHDzefatacbGxjh8+DD279+PkSNH4sKFCxgyZAh69uypHOjq7e2N69evY+PGjejUqRN+//13dOrUCXPnzhU5eqLqo4ttR3FxMXr27ImdO3fiww8/REREBCIjI7Fu3ToA6p1fZVWUhPx3YH2p8v5ebt26hR49eiA9PR1LlizBzp07ERkZiXfffRdA5eO2trbGSy+9pEyatmzZgvz8fGVyXNdwIDg9VXR0NB4+fIg//vgDXbp0UZbfuXNHxKj+4eDgACMjI8TFxZV5r7wydbi5uWH//v3IyspS6W0qvZ3g5uamLJNKpejRowd69OiBJUuWYMGCBZg5cyYOHjyIwMBAAICpqSmGDBmCIUOGoKCgAIMGDcJnn32GsLCwujdIkuhvdb3tuHjxIm7cuIGffvoJISEhyvLIyEiVeg0bNgQAXLp0qcJj2dvbw8LC4ql1gH962h4/fqwcHgD80/utjh07diA/Px/bt29X6Y07ePCgSr3SW5uXLl16Zs9bSEgIXn75ZZw6dQrr169Hy5Yt0bRpU7Vjqk3Y00RPVfpr7d+/zgoKCvDdd9+JFZIKPT09BAYGIiIiAvfv31eWx8XFKcceVVbfvn1RXFyMZcuWqZQvXboUEolEOd4hIyOjzL4tWrQAAOTn5wMoearo3wwNDeHj4wNBEFBYWFil+Ihqg7redpR3foIg4Ouvv1apZ29vjy5dumDNmjVITExUea90X6lUiuDgYOzYsQOnT58u81ml9UoTmcOHDyvfy8nJwU8//fTMeJ8Wd2ZmJtauXatSr1evXjA3N0d4eHiZaQP+21vXp08f2NnZ4fPPP8ehQ4fqbC8TwJ4meoYOHTrA2toao0aNwpQpUyCRSPC///1PK26Plfr444+xb98+dOzYERMmTFAmPL6+voiNja308fr374/u3btj5syZiI+Ph5+fH/bt24dt27Zh2rRpyoZr/vz5OHz4MPr16wc3NzekpaXhu+++Q/369dGpUycAJQ2Pk5MTOnbsCEdHR1y9ehXLli1Dv379yoyZIqpL6nrb4eXlhRdeeAHvvfce7t27BwsLC/z+++/ljrf65ptv0KlTJ7Rq1Qrjxo2Dh4cH4uPjsXPnTuXnLFiwAPv27UPXrl0xbtw4eHt7Izk5GZs3b8aRI0dgZWWFXr16oUGDBhgzZgzef/996OnpYc2aNbC3ty+TkFWkV69eMDQ0RP/+/fH2228jOzsbq1atgoODA5KTk5X1LCwssHTpUrz11lto27Ythg8fDmtra5w/fx65ubkqiZqBgQGGDh2KZcuWQU9PD8OGDVMrltqISRM9la2tLf78809Mnz4ds2bNgrW1NV5//XX06NGj3KcyxNC6dWvs3r0b7733HmbPng1XV1fMnz8fV69eVesJnf+SSqXYvn075syZg02bNmHt2rVwd3fHl19+ienTpyvrDRgwAPHx8VizZg3S09NhZ2eHrl27Yt68ebC0tAQAvP3221i/fj2WLFmC7Oxs1K9fH1OmTMGsWbOq7fyJtFFdbzsMDAywY8cOTJkyBeHh4TAyMsLAgQMxefJk+Pn5qdT18/PD8ePHMXv2bKxYsQJ5eXlwc3PDa6+9pqxTr149nDhxArNnz8b69eshl8tRr1499OnTRzm7uoGBAbZu3YqJEydi9uzZcHJywrRp02BtbV3u04vl8fT0xJYtWzBr1iy89957cHJywoQJE2Bvb48333xTpe6YMWPg4OCAhQsX4pNPPoGBgQG8vLyU45/+LSQkBMuWLUOPHj3g7OysViy1EZdRoTorODgYly9fxs2bN8UOhYhqEbYdlXf+/Hm0aNECP//8M0aOHCl2ODWGY5qoTvjvsgU3b97Erl270K1bN3ECIqJagW1H9Vi1ahXMzMwwaNAgsUOpUbw9R3VCw4YN8cYbb6Bhw4ZISEjAihUrYGhoiA8++EDs0IhIi7HteD47duzAlStX8MMPP2Dy5MkwNTUVO6QaxdtzVCeMHj0aBw8eREpKCmQyGQICArBgwQK0atVK7NCISIux7Xg+7u7uSE1NRVBQEP73v//V+QdcmDQRERERqYFjmoiIiIjUwKSJiIiISA06NxBcoVDg/v37MDc3r5OLCRJpI0EQkJWVBRcXF0il/K32b2yTiDSvqm2SziVN9+/fh6urq9hhEOmkpKQk1K9fX+wwtArbJCLxVLZN0rmkqXRkf1JSEiwsLESOhkg3yOVyuLq61vkna6qCbRKR5lW1TdK5pKm0+9vCwoINFJGG8fZTWWyTiMRT2TaJgwuIiIiI1MCkiYiIiEgNTJqIiP52+PBh9O/fHy4uLpBIJIiIiHjmPtHR0WjVqhVkMhkaNWqEdevW1XicRCQOJk1ERH/LycmBn58fli9frlb9O3fuoF+/fujevTtiY2Mxbdo0vPXWW9i7d28NR0pEYtC5geBERBXp06cP+vTpo3b9lStXwsPDA4sXLwYAeHt748iRI1i6dCmCgoJqKkwiEgmTpgrce/wEK6LjMLOvD4wN9cQOh4i0UExMDAIDA1XKgoKCMG3aNHECogolZeQi+sYDcLnVus/WVIZ+zZ1r5NhMmsqhUAgYteYk4tKyYW1iiOm9PMUOiYi0UEpKChwdHVXKHB0dIZfL8eTJExgbG5fZJz8/H/n5+crXcrm8xuMkIPS3WJyKfyR2GKQBfvUtmTRpklQqwXu9mmD8L2fx/aHbGNSqPjzsTMUOi4jqgPDwcMybN0/sMHROwsNcAEDnxnawMDIQORqqSQ1sTWrs2EyaKhDU1Aldm9jj0I0HmLn1In4Z4w+plBPzEdE/nJyckJqaqlKWmpoKCwuLcnuZACAsLAyhoaHK16UzE1PNEQQBj3ILAACfv9IcLlbl/90QPQufnquARCLBvAFNYWQgxbFbD/FTTLzYIRGRlgkICEBUVJRKWWRkJAICAircRyaTKWf/5izgmiHPK0JhcclYJhtTQ5GjodqMSdNTuNuZYmZfbwDAwt3XcDM1S+SIiKgmZWdnIzY2FrGxsQBKphSIjY1FYmIigJJeopCQEGX98ePH4/bt2/jggw9w7do1fPfdd/jtt9/w7rvvihE+VeBRTkkvk4mhHowM+GAPVR2Tpmd4vb0bujSxR36RAu/+FouCIoXYIRFRDTl9+jRatmyJli1bAgBCQ0PRsmVLzJkzBwCQnJysTKAAwMPDAzt37kRkZCT8/PywePFirF69mtMNaJmHfydN7GWi58UxTc8gkUjw5avNEfTVYVy6J8e3B27yaTqiOqpbt25PfSS9vNm+u3XrhnPnztVgVPS8HjFpomrCniY1OFoY4bPgZgCA5QfjcCaBj60SEdUWGUyaqJowaVJTv+bOGNiyHhQCMP23WOTkF4kdEhERqSEjl0kTVQ8mTZXw8YCmcLE0QvzDXHy266rY4RARkRqUPU0mTJro+TBpqgRLYwMsGuwHANhwIhEHrqU+Yw8iIhKbMmkyY9JEz4dJUyV1aGSHMZ08AAAfbLmIh9n5z9iDiIjExJ4mqi5Mmqrg/SBPeDqaIz07Hx/9cZELQBIRaTEOBKfqwqSpCowM9LB0SAsY6kkReSUVv51OEjskIiKqAJMmqi6iJk0rVqxA8+bNlUsJBAQEYPfu3U/dZ/PmzfDy8oKRkRGaNWuGXbt2aShaVT4uFpjeqwkAYP6OK7j7KFeUOIiI6Ok4TxNVF1GTpvr162PhwoU4c+YMTp8+jRdffBEvv/wyLl++XG79Y8eOYdiwYRgzZgzOnTuH4OBgBAcH49KlSxqOvMRbnRuirbs1cgqK8eHvF3ibjohIy+QXFSPr7ylimDTR85IIWvY/vY2NDb788kuMGTOmzHtDhgxBTk4O/vzzT2VZ+/bt0aJFC6xcuVKt48vlclhaWiIzM7NaFsq8k56DPl8fRl6hAp8G++L19m7PfUyiuqa6v3d1Ca/N80uT5+HXk0nIKyou896TgmKsOxYPPakENz/tA6lUIkKEpG2q+r3TmmVUiouLsXnzZuTk5FS4QnhMTAxCQ0NVyoKCghAREVHhcfPz85Gf/88TbnK5vFriLeVhZ4oPgrww/88rCN91FV2b2MPVxqRaP4OIiCr2XfQtrDsW/9Q6ThZGTJjouYmeNF28eBEBAQHIy8uDmZkZtm7dCh8fn3LrpqSkwNHRUaXM0dERKSkpFR4/PDwc8+bNq9aY/+uNDu7YcykFJ+Mz8OHvF/DLGH9+OYmINOTe4ycAgE6N7NDE0bzcOr2aOpZbTlQZoidNnp6eiI2NRWZmJrZs2YJRo0bh0KFDFSZOlRUWFqbSOyWXy+Hq6lotxy4llUrwxavN0fvrwzh26yHWn0zESN6mIyLSiNKB3iP8G6BPM2eRo6G6TPQpBwwNDdGoUSO0bt0a4eHh8PPzw9dff11uXScnJ6Smqs7CnZqaCicnpwqPL5PJlE/nlW41wd3OFB/29gIAhO+6iqQMPk1HRKQJpVMKWHOgN9Uw0ZOm/1IoFCpjkP4tICAAUVFRKmWRkZEVjoHStFEB7mjnYYPcgmK8v+U8FAqtGmNPRFQnlS7Ia8ukiWqYqElTWFgYDh8+jPj4eFy8eBFhYWGIjo7GiBEjAAAhISEICwtT1p86dSr27NmDxYsX49q1a/j4449x+vRpTJ48WaxTUCGVSvDlq81hbKCH47cz8MuJBLFDIiKq04qKFXicWwiAPU1U80RNmtLS0hASEgJPT0/06NEDp06dwt69e9GzZ08AQGJiIpKTk5X1O3TogA0bNuCHH36An58ftmzZgoiICPj6+op1CmW42Zrioz6lt+muIfEhb9MREdWUR38nTBIJYGVsIHI0VNeJOhD8xx9/fOr70dHRZcoGDx6MwYMH11BE1WNkezfsvpSM47czMGPrRfxvTDtIJHyajoioOuUVFiP+YQ4AwNLYAPp6WjfihOoY/gurAVKpBJ+/0hyG+lIciUvHjgvJz96JiIjUlvgwF20+3Y/BK2MAcLZv0gwmTTXEzdYUk7o1AgB88ucVyPMKRY6IiKjuOJf0CNl/L48ikQD9ONUAaQCTpho0vltDeNiZ4kFWPpbsuyF2OEREdcbD7JIn5vo1d8btBX0xvZenyBGRLmDSVINk+nr45OWSQeo/x8Tj0r1MkSMiIqobHv1rmgGOGSVNYdJUwzo1tkN/PxcoBGDm1oso5txNRETP7WHphJYmHMtEmsOkSQNm9/OGuUwf5+9mYsPJRLHDISKq9UqXTrE1Y9JEmsOkSQMcLIwwvVcTAMAXe67hQVb5M54TEZF62NNEYmDSpCEjA9zhW88CWXlFWLDrqtjhEBHVasqeJk41QBrEpElD9KQSfBbcDBIJsPXcPRy7lS52SEREtcrVZDk+3HIBoZtikfSoZLUFLp1CmsSkSYP8XK0wwr8BAODj7ZdRVKwQOSIiotrjm6ib2HQ6CX+cu4e8QgX0pRI4WxqJHRbpEFGXUdFF7/fywp8XknEjNRu/nkrCyPZuYodERFQrpMrzAACDW9dHE0dzNHWxgBXHNJEGsadJwyxNDPBuYMmg8KWRN5D5hDOFExGpo3Rx3sFtXDG2S0N0aGQnckSka5g0iWC4fwO8YG+KjJwCLD8YJ3Y4RPQvy5cvh7u7O4yMjODv74+TJ08+tf5XX30FT09PGBsbw9XVFe+++y7y8vI0FK1ueZhd8uSxjamByJGQrmLSJAIDPSlmveQDAFh79A7i03NEjoiIAGDTpk0IDQ3F3LlzcfbsWfj5+SEoKAhpaWnl1t+wYQM++ugjzJ07F1evXsWPP/6ITZs2YcaMGRqOvO4rLFZAnley1pyNqUzkaEhXMWkSSXdPB3RpYo/CYgHhuzkFAZE2WLJkCcaOHYvRo0fDx8cHK1euhImJCdasWVNu/WPHjqFjx44YPnw43N3d0atXLwwbNuyZvVNUeY//vjUnkQCWxuxpInEwaRLRrH7e0JNKsPdyKmJuPRQ7HCKdVlBQgDNnziAwMFBZJpVKERgYiJiYmHL36dChA86cOaNMkm7fvo1du3ahb9++FX5Ofn4+5HK5ykbPlvH3vExWxgbQk3KtORIHn54TURNHcwxv1wD/O56AT/68gh3vdGJjQCSS9PR0FBcXw9HRUaXc0dER165dK3ef4cOHIz09HZ06dYIgCCgqKsL48eOfensuPDwc8+bNq9bY64pihYCbaVnlrtFZuuC5DedlIhExaRLZuz2bICL2Hq4ky/H7mbt4ra2r2CERkZqio6OxYMECfPfdd/D390dcXBymTp2KTz75BLNnzy53n7CwMISGhipfy+VyuLryew8A7285jz/O3ntqHSZNJCYmTSKzMTXE1B6N8enOq/hi73X0be4MMxn/Wog0zc7ODnp6ekhNTVUpT01NhZOTU7n7zJ49GyNHjsRbb70FAGjWrBlycnIwbtw4zJw5E1Jp2REQMpkMMhkHMpfnfNJjACXtooFe2V53fakUQ9s20HBURP/g/85aICTAHb8cT0D8w1ysOnwb7/ZsInZIRDrH0NAQrVu3RlRUFIKDgwEACoUCUVFRmDx5crn75ObmlkmM9PT0AACCUPYWEz1d6TxMv45tD08nc5GjISqLA8G1gKG+FB/09gIArPrrNtLknOOFSAyhoaFYtWoVfvrpJ1y9ehUTJkxATk4ORo8eDQAICQlBWFiYsn7//v2xYsUKbNy4EXfu3EFkZCRmz56N/v37K5MnUk+xQsCj3JLB3tach4m0lKhJU3h4ONq2bQtzc3M4ODggODgY169ff+o+69atg0QiUdmMjGr/2kN9fJ3QsoEVcguKsXT/TbHDIdJJQ4YMwaJFizBnzhy0aNECsbGx2LNnj3JweGJiIpKTk5X1Z82ahenTp2PWrFnw8fHBmDFjEBQUhO+//16sU6i1Mp8UorRzzppLo5CWkggi9iH37t0bQ4cORdu2bVFUVIQZM2bg0qVLuHLlCkxNTcvdZ926dZg6dapKciWRSMo88VIRuVwOS0tLZGZmwsLColrOo7qcjs/AqytjIJUA+97tgkYO7J6mukGbv3di47UpEZeWhcAlh2FhpI8LHweJHQ7VcVX93ok6pmnPnj0qr9etWwcHBwecOXMGXbp0qXA/iURS4cDM2qyNuw2Cmjpi7+VULNx9DatHtRU7JCIijcjIKRnPxKfjSJtp1ZimzMy/5+GwsXlqvezsbLi5ucHV1RUvv/wyLl++rInwNOKD3l7Qk0qw/2oajt/mhJdEpBsyckrXlWPSRNpLa5ImhUKBadOmoWPHjvD19a2wnqenJ9asWYNt27bhl19+gUKhQIcOHXD37t1y69e22XdfsDfD8HYlj9SG77oKRTmTvBER1SWCIOCjPy4CYNJE2k1rkqZJkybh0qVL2Lhx41PrBQQEICQkBC1atEDXrl3xxx9/wN7evsKBl+Hh4bC0tFRutWESuSk9GsPUUA/n72Zi58XkZ+9ARFSLxT/MVa4t94K9mcjREFVMK5KmyZMn488//8TBgwdRv379Su1rYGCAli1bIi4urtz3w8LCkJmZqdySkpKqI+QaZW8uw/iuLwAAvth7DflFxSJHRERUc9Kz85V/nhbIeepIe4maNAmCgMmTJ2Pr1q04cOAAPDw8Kn2M4uJiXLx4Ec7OzuW+L5PJYGFhobLVBmM6e8DBXIakjCf45Xii2OEQEdWY0sV4WzawgrEh57ci7SVq0jRp0iT88ssv2LBhA8zNzZGSkoKUlBQ8efJEWee/k8nNnz8f+/btw+3bt3H27Fm8/vrrSEhIUC5jUFeYGOpjeq+SX1zfHriJzCeFIkdERFQzSpMmG87PRFpO1KRpxYoVyMzMRLdu3eDs7KzcNm3apKzz38nkHj16hLFjx8Lb2xt9+/aFXC7HsWPH4OPjI8Yp1KhXW7uiiaMZHucW4rvo8m8/EhHVdsqkiYPAScuJOk+TOvNqRkdHq7xeunQpli5dWkMRaRc9qQRhfbwxet0prD0aj5Ht3VDf2kTssIiIqhWTJqottGIgOFWsm6c9AhraoqBIgSX7bogdDhFRtXvEpIlqCVF7mujZJBIJZvT1Rv9lR7A19h7e7OQB33qWYodFRPRUl+9nIiVTvcXHbz3IBgBYM2kiLcekqRZoVt8SL7dwwbbY+wjffRW/jPGHRCIROywionJduS9Hv2+OVHo/OzMmTaTdmDTVEu/18sTuiyk4GvcQh248QDdPB7FDIiIq1820LACAuZE+Gqo5WaWLpRECGtrVZFhEz41JUy3hamOCUR3csOqvO1i4+xo6N7aHnpS9TUSkfUoHdndpYo/lw1uJHA1R9eFA8FpkUvdGsDDSx7WULPxxtvy19oiIxMZ5l6iuYtJUi1iZGOKdFxsDABbvu4EnBVxehYi0D6cQoLqKSVMtMzLADfWsjJEiz8Oao3fEDodIdO7u7pg/fz4SE7nckLZg0kR1FZOmWsbIQA8f9PYEAKyIvoWH/1rokkgXTZs2DX/88QcaNmyInj17YuPGjcjP5/dCTEyaqK5i0lQL9W/uAt96FsjOL8K3B7i8Cum2adOmITY2FidPnoS3tzfeeecdODs7Y/LkyTh79qzY4ekkJk1UVzFpqoWk0pIJLwHgl+MJuJOeI3JEROJr1aoVvvnmG9y/fx9z587F6tWr0bZtW7Ro0QJr1qxRa9kmej6Pcwsw5PsY3Ez7e7JKDgSnOoZJUy3V4QU7dPe0R5FCwJd7r4kdDpHoCgsL8dtvv2HAgAGYPn062rRpg9WrV+OVV17BjBkzMGLECLFDrPOOxKXjxJ0MAICZTB8NbLlWJtUtnKepFvuojzcO3XiAXRdTcCbhEVq7WYsdEpHGnT17FmvXrsWvv/4KqVSKkJAQLF26FF5eXso6AwcORNu2bUWMUjeU3pazN5dh15TOMJPxvxiqW9jTVIt5OpljcGtXAED4rqu8/UA6qW3btrh58yZWrFiBe/fuYdGiRSoJEwB4eHhg6NChIkWoOx5mlyRNvXwcYW8uEzkaourHnwG1XGivJth2/h5OJzzC3sup6O3rJHZIRBp1+/ZtuLm5PbWOqakp1q5dq6GIdBcHgFNdx56mWs7RwghjOzcEAHyx5xoKixUiR0SkWWlpaThx4kSZ8hMnTuD06dMiRKS7MnKZNFHdxqSpDni76wuwNTXE7fQcbDzJCf5It0yaNAlJSUllyu/du4dJkyaJEJHuyshm0kR1G5OmOsBMpo9pgSXLq3y1/yay84tEjohIc65cuYJWrcouCtuyZUtcuXJFhIh01yP2NFEdxzFNdcTQdg2w9mg8bqfn4PtDtzC9l6fYIRFphEwmQ2pqKho2bKhSnpycDH19NnE1oVghYN/lFDz8ewxTqfuPnwDg/ExUd7FFqSMM9KT4oLcXxv9yBqv+uo3X27vB0cJI7LCIalyvXr0QFhaGbdu2wdLSEgDw+PFjzJgxAz179hQ5urop8koKJqyveLZ1Bz45R3UUb8/VIUFNHdHGzRp5hQos2XdD7HCINGLRokVISkqCm5sbunfvju7du8PDwwMpKSlYvHhxpY+3fPlyuLu7w8jICP7+/jh58uRT6z9+/BiTJk2Cs7MzZDIZmjRpgl27dlX1dGqF+Ie5AIB6VsYIauqosn3UxwsO/MFGdRR7muoQiUSCsL7eeGXFMfx2Jgmvt3dDs/qWYodFVKPq1auHCxcuYP369Th//jyMjY0xevRoDBs2DAYGBpU61qZNmxAaGoqVK1fC398fX331FYKCgnD9+nU4ODiUqV9QUICePXvCwcEBW7ZsQb169ZCQkAArK6tqOjvtVDq1QB9fJ8x6yUfkaIg0R9SepvDwcLRt2xbm5uZwcHBAcHAwrl+//sz9Nm/eDC8vLxgZGaFZs2Z1/lddZbR2s8bLLVwgCMDsbZegUHDCS6r7TE1NMW7cOCxfvhyLFi1CSEhIpRMmAFiyZAnGjh2L0aNHw8fHBytXroSJiQnWrFlTbv01a9YgIyMDERER6NixI9zd3dG1a1f4+fk97ylptdKkyZoDvknHiJo0HTp0CJMmTcLx48cRGRmJwsJC9OrVCzk5FS9Ae+zYMQwbNgxjxozBuXPnEBwcjODgYFy6dEmDkWu3GX29YSbTR2zSY2w+U/ZRbKK66MqVK9izZw+2b9+usqmroKAAZ86cQWBgoLJMKpUiMDAQMTEx5e6zfft2BAQEYNKkSXB0dISvry8WLFiA4uLi5z4fbVaaNNkyaSIdI+rtuT179qi8XrduHRwcHHDmzBl06dKl3H2+/vpr9O7dG++//z4A4JNPPkFkZCSWLVuGlStX1njMtYGjhRGmBTbGpzuvYuHuawhq6gQrPs1CddTt27cxcOBAXLx4ERKJRLmckEQiAQC1E5j09HQUFxfD0dFRpdzR0RHXrpW/KPbt27dx4MABjBgxArt27UJcXBwmTpyIwsJCzJ07t9x98vPzkZ+fr3wtl8vVik+bsKeJdFWVepqSkpJw9+5d5euTJ09i2rRp+OGHH54rmMzMTACAjY1NhXViYmJUfgkCQFBQUIW/BHXVqA7u8HQ0x6PcQny599m3PIlqq6lTp8LDwwNpaWkwMTHB5cuXcfjwYbRp0wbR0dE1+tkKhQIODg744Ycf0Lp1awwZMgQzZ8586g+48PBwWFpaKjdXV9cajbEmsKeJdFWVkqbhw4fj4MGDAICUlBT07NkTJ0+exMyZMzF//vwqBaJQKDBt2jR07NgRvr6+FdZLSUkp95dgSkpKufXz8/Mhl8tVNl1goCfF/JebAgA2nEzEhbuPxQ2IqIbExMRg/vz5sLOzg1QqhVQqRadOnRAeHo4pU6aofRw7Ozvo6ekhNTVVpTw1NRVOTuWv6ejs7IwmTZpAT09PWebt7Y2UlBQUFBSUu09YWBgyMzOVW3mzmWu7R+xpIh1VpaTp0qVLaNeuHQDgt99+g6+vL44dO4b169dj3bp1VQpk0qRJuHTpEjZu3Fil/StSF37VVZV/Q1sElw4Kj7iEYg4KpzqouLgY5ubmAEoSn/v37wMA3Nzc1HqwpJShoSFat26NqKgoZZlCoUBUVBQCAgLK3adjx46Ii4uDQvHPmo83btyAs7MzDA3LTyhkMhksLCxUttriRmoW2n62H1l/rzrAnibSNVVKmgoLCyGTlUxetn//fgwYMAAA4OXlheTk5Eofb/Lkyfjzzz9x8OBB1K9f/6l1nZycKvVLsC78qnseM/p6w1ymj/N3M/G/mHixwyGqdr6+vjh//jwAwN/fH1988QWOHj2K+fPnl5kl/FlCQ0OxatUq/PTTT7h69SomTJiAnJwcjB49GgAQEhKCsLAwZf0JEyYgIyMDU6dOxY0bN7Bz504sWLCgzq55dzQuHQ+ySsZjeTqaw8Ko8k8oEtVmVUqamjZtipUrV+Kvv/5CZGQkevfuDQC4f/8+bG1t1T6OIAiYPHkytm7digMHDsDDw+OZ+wQEBKj8EgSAyMjICn8J1uZfddXBwcIIH/QuWVLly73XlcscENUVs2bNUvb0zJ8/H3fu3EHnzp2xa9cufPPNN5U61pAhQ7Bo0SLMmTMHLVq0QGxsLPbs2aMcEpCYmKjyw9DV1RV79+7FqVOn0Lx5c0yZMgVTp07FRx99VH0nqEVyC0oG1Qc0tMWfUzpBKpWIHBGRZkmE0kdNKiE6OhoDBw6EXC7HqFGjlHOYzJgxA9euXcMff/yh1nEmTpyIDRs2YNu2bfD0/GetNEtLSxgbGwMo+WVXr149hIeHAyiZcqBr165YuHAh+vXrh40bN2LBggU4e/bsU8dClZLL5bC0tERmZqbOJFAKhYDB38fgTMIj9PBywOpRbZRPFhFpgqa/dxkZGbC2tq4V/85rU5u0eN91fHsgDqMC3DDv5We3t0Taqqrfuyr1NHXr1g3p6elIT09XmfRt3LhxlXrsf8WKFcjMzES3bt3g7Oys3DZt2qSs899fdh06dMCGDRvwww8/wM/PD1u2bEFERIRaCZOukkolWDioGQz0JIi6loadFyt/C5VIGxUWFkJfX7/MPG02Nja1ImGqbZ783dNkZKj3jJpEdVOV5ml68uQJBEGAtbU1ACAhIQFbt26Ft7c3goKC1D6OOp1c5T0yPHjwYAwePFjtzyGgsaM5JnRrhG+ibuLj7VfQuZE9LE04HoFqNwMDAzRo0KDOTyapLZ4UllxnYwMmTaSbqtTT9PLLL+Pnn38GULJYpb+/PxYvXozg4GCsWLGiWgOk6jOp+wt4wd4U6dn5WLDrqtjhEFWLmTNnYsaMGcjIyBA7lDqvtKeJSRPpqiolTWfPnkXnzp0BAFu2bIGjoyMSEhLw888/V3rgJWmOTF8PC19pDgDYdDoJh248EDkioue3bNkyHD58GC4uLvD09ESrVq1UNqo+pT1NJrw9RzqqSrfncnNzlfOi7Nu3D4MGDYJUKkX79u2RkJBQrQFS9WrrboNRAW74KSYBH2w5j73TunCJFarVgoODxQ5BZ5QmTUbsaSIdVaWkqVGjRoiIiMDAgQOxd+9evPvuuwCAtLQ0rX/6g4CP+njjr7h03H6Qg9nbLuPbYS3FDomoyipa442qn/L2HHuaSEdV6fbcnDlz8N5778Hd3R3t2rVTzpG0b98+tGzJ/4C1nbGhHpa+1gJ6Ugl2nL+P7efvix0SEdUCHAhOuq5KSdOrr76KxMREnD59Gnv37lWW9+jRA0uXLq224Kjm+LlaYXL3RgCAWVsvIiUzT+SIiKpGKpVCT0+vwo2qD3uaSNdV6fYcULKciZOTE+7evQsAqF+/vnI9OqodJr/YCAevp+HC3Uy8v+U8fn6zHee2oVpn69atKq8LCwtx7tw5/PTTT5g3b55IUdVN7GkiXVelpEmhUODTTz/F4sWLkZ2dDQAwNzfH9OnTMXPmTEilVerAIg0z0JNiyWst0O+bv/DXzXT8cjwBIwPcxQ6LqFJefvnlMmWvvvoqmjZtik2bNmHMmDEiRFU35RWyp4l0W5Wym5kzZ2LZsmVYuHAhzp07h3PnzmHBggX49ttvMXv27OqOkWpQIwczfNTHCwDw2a6ruP0gW+SIiKpH+/bty6xTSc8nl/M0kY6rUtL0008/YfXq1ZgwYQKaN2+O5s2bY+LEiVi1ahXWrVtXzSFSTRsV4I6OjWyRV6jAu7+dR2GxQuyQiJ7LkydP8M0336BevXpih1JnCILwz+059jSRjqrS7bmMjAx4eXmVKffy8uKsvLWQVCrBl6/6Ieirwzif9BhLIm/gw95l/36JtNF/F+YVBAFZWVkwMTHBL7/8ImJktcuTgmIUFFX8gym/qBilK1+xp4l0VZWSJj8/PyxbtqzM7N/Lli1D8+bNqyUw0iwXK2N8/kpzTFx/Fiuib6Gdhw26ezqIHRbRMy1dulQlaZJKpbC3t4e/v79yfUx6uj2XkjF5wzkUKZ69HijAyS1Jd1Upafriiy/Qr18/7N+/XzlHU0xMDJKSkrBr165qDZA0p28zZ4QEuOHnmASEborFrqmd4WxpLHZYRE/1xhtviB1CrXckLl3thOlFLwcY6PFhH9JNVUqaunbtihs3bmD58uW4du0aAGDQoEEYN24cPv30U+W6dFT7zOjrjTMJj3D5vhxTfj2HX8e2hz4bSNJia9euhZmZGQYPHqxSvnnzZuTm5mLUqFEiRVZ7ZOQUAADmvOSDkAC3p9Zle0C6rMr/+l1cXPDZZ5/h999/x++//45PP/0Ujx49wo8//lid8ZGGGRnoYfnwVjCT6eNU/CMsibwhdkhETxUeHg47O7sy5Q4ODliwYIEIEdU+pUmTnbkM+nrSp25EuozfACrD3c4UC19pBgD4LvoW9l9JFTkiooolJibCw8OjTLmbmxsSExNFiKj2KU2abLh4N9FTMWmicr3U3EXZTT9tUyzi0jh/E2knBwcHXLhwoUz5+fPnYWtrK0JEtU9GTiEAwNrUQORIiLQbkyaq0Kx+PmjnboPs/CKM+/k05HmFYodEVMawYcMwZcoUHDx4EMXFxSguLsaBAwcwdepUDB06VOzwtJ5CIeBRbklPk62pTORoiLRbpQaCDxo06KnvP378+HliIS1jqC/Fd6+3Qv9vj+B2eg6mbYzF6pA2kEq5Ph1pj08++QTx8fHo0aMH9PVLmjSFQoGQkBCOaVJDVl4Riv9+co49TURPV6mkydLS8pnvh4SEPFdApF3szGT4YWQbvLryGA5cS8PS/TcwvZen2GERKRkaGmLTpk349NNPERsbC2NjYzRr1gxubk9/CoxK/HGuZNF1M5k+ZPqcf4noaSqVNK1du7am4iAt1qy+JRa+0gzvbjqPbw/EwcfZAn2aOYsdFpGKxo0bo3HjxmKHUet8HXUTAGBuVKUZaIh0Csc0kVoGtqyPtzqVPKE0ffN5XEuRixwRUYlXXnkFn3/+eZnyL774oszcTaRKEARk5xUBAD5/has5ED2LqEnT4cOH0b9/f7i4uEAikSAiIuKp9aOjoyGRSMpsKSkpmglYx33UxwudGtkht6AYb649hVR5ntghEeHw4cPo27dvmfI+ffrg8OHDIkRUe8jzipQzgbfzsBE5GiLtJ2rSlJOTAz8/PyxfvrxS+12/fh3JycnKzcGBa6Rpgr6eFMuGt0RDe1Pcz8zD6LWnkJ1fJHZYpOOys7NhaFh2fiEDAwPI5ewRfZpHf8/PZGqox/XkiNQgatLUp08ffPrppxg4cGCl9nNwcICTk5Nyk0p5l1FTrEwMse6NdrAzM8SVZDkmrj+LwuKKV0YnqmnNmjXDpk2bypRv3LgRPj4+IkRUezz8O2myNuWklkTqqJUj/1q0aIH8/Hz4+vri448/RseOHSusm5+fj/z8fOVr/vJ8fg1sTfDjqLYY+sNxHL7xALMjLiF8UDOVleaJNGX27NkYNGgQbt26hRdffBEAEBUVhQ0bNmDLli0iR6fdSnuabJk0EamlVnXRODs7Y+XKlcr17lxdXdGtWzecPXu2wn3Cw8NhaWmp3FxdXTUYcd3l52qFb4e1hFQCbDyVhGUH4sQOiXRU//79ERERgbi4OEycOBHTp0/HvXv3cODAATRq1Ejs8LRaBnuaiCqlViVNnp6eePvtt9G6dWt06NABa9asQYcOHbB06dIK9wkLC0NmZqZyS0pK0mDEdVugjyPmDWgKAFgceQO/n7krckSkq/r164ejR48iJycHt2/fxmuvvYb33nsPfn5+Yoem1TL+ngnchkkTkVpqVdJUnnbt2iEuruJeDplMBgsLC5WNqs/IAHe83bUhAODD3y/gwDUu7kviOHz4MEaNGgUXFxcsXrwYL774Io4fP17p4yxfvhzu7u4wMjKCv78/Tp48qdZ+GzduhEQiQXBwcKU/UyxcqJeocmp90hQbGwtnZ060KKYPg7wQ3MIFRQoBE345i5N3MsQOiXRESkoKFi5ciMaNG2Pw4MGwsLBAfn4+IiIisHDhQrRt27ZSx9u0aRNCQ0Mxd+5cnD17Fn5+fggKCkJaWtpT94uPj8d7772Hzp07P8/paJwyaTJj0kSkDlGTpuzsbMTGxiI2NhYAcOfOHcTGxiIxMRFAya21fy/L8tVXX2Hbtm2Ii4vDpUuXMG3aNBw4cACTJk0SI3z6m1QqwZeD/dDDywH5RQqMWXcKl+5lih0W1XH9+/eHp6cnLly4gK+++gr379/Ht99++1zHXLJkCcaOHYvRo0fDx8cHK1euhImJCdasWVPhPsXFxRgxYgTmzZuHhg0bPtfna9oj9jQRVYqoSdPp06fRsmVLtGzZEgAQGhqKli1bYs6cOQCA5ORkZQIFAAUFBZg+fTqaNWuGrl274vz589i/fz969OghSvz0DwM9KZaPaAV/Dxtk5RchZM1JxKVliR0W1WG7d+/GmDFjMG/ePPTr1w96es83z1BBQQHOnDmDwMBAZZlUKkVgYCBiYmIq3G/+/PlwcHDAmDFj1Pqc/Px8yOVylU0spVMOcEwTkXpETZq6desGQRDKbOvWrQMArFu3DtHR0cr6H3zwAeLi4vDkyRM8fPgQBw8eRPfu3cUJnsowMtDD6lFt0KyeJTJyCjB81QncfpAtdlhURx05cgRZWVlo3bo1/P39sWzZMqSnp1f5eOnp6SguLoajo6NKuaOjY4WrDhw5cgQ//vgjVq1apfbnaNMTvY84EJyoUmr9mCbSLuZGBvjpzXbwcjJHWlY+hq86gYSHOWKHRXVQ+/btsWrVKiQnJ+Ptt9/Gxo0b4eLiAoVCgcjISGRl1WxPZ1ZWFkaOHIlVq1bBzs5O7f206YnejGxOOUBUGUyaqNrZmBril7f80djBDCnyPAz74TiSMnLFDovqKFNTU7z55ps4cuQILl68iOnTp2PhwoVwcHDAgAED1D6OnZ0d9PT0kJqq+gRoamoqnJycytS/desW4uPj0b9/f+jr60NfXx8///wztm/fDn19fdy6davcz9GWJ3oLihTI+nsZJE5uSaQeJk1UI+zMZFg/1l+5Tt2wVcdx7/ETscOiOs7T0xNffPEF7t69i19//bVS+xoaGqJ169aIiopSlikUCkRFRSEgIKBMfS8vL1y8eFH5MEtsbCwGDBiA7t27IzY2Vusn0i29NacnlcDCyEDkaIhqh1q5jArVDg7mRvh1bHsM/eE47qTnYNgPx7FhrD/qW5uIHRrVcXp6eggODq70nEmhoaEYNWoU2rRpg3bt2uGrr75CTk4ORo8eDQAICQlBvXr1EB4eDiMjI/j6+qrsb2VlBQBlyrWRcjZwEwNIpVwCiUgd7GmiGuVoYYQNY/3RwMYEiRm5eG1lDO6kc4wTaachQ4Zg0aJFmDNnDlq0aIHY2Fjs2bNHOTg8MTERycnJIkdZPf5JmnhrjkhdEkEQBLGD0CS5XA5LS0tkZmZydnANSs58ghGrT+D2g5ySW3dv+cPTyVzssEhD+L2rmFjXZsf5+3jn13Pw97DBprfL3n4kqsuq+r1jTxNphLOlMX57OwBeTuZIz87H0B9icPEuJ8AkEgunGyCqPCZNpDF2ZjJsHNcefq5WeJRbiOGrjiPm1kOxwyLSSQ+zmTQRVRaTJtIoKxND/DKmHdr9PXP4qDUnsfti3RgjQlSbsKeJqPKYNJHGmRsZ4Oc32yGoqSMKihWYuOEs/nc8QeywiHQKl1AhqjwmTSQKIwM9fDeiNYb7N4AgALMjLmHJvuvQsecSiETziEkTUaUxaSLR6Ekl+CzYF9MCGwMAvjkQh49+v4jCYoXIkRHVbTdSs3Ds7/GETJqI1MekiUQlkUgwLbAJPhvoC6kE2HQ6CaPWnERmbqHYoRHVSXFpWei19LDyNZMmIvUxaSKtMMLfDatC2sDUUA/Hbj3EwO+OIp6TYBJVu+sp2co/D2pVD95OnDeLSF1Mmkhr9PB2xJYJHeBiaYTb6TkI/u4ojt/mlARE1Snj76fmevk4YslrLbiEClElMGkireLtbIGISR3hV98Sj3MLMfLHE9h8OknssIjqjNIB4LZmvC1HVFlMmkjrOFgYYdPbAejXzBmFxQLe33IBc7ZdQn5RsdihEdV6XHOOqOqYNJFWMjLQw7fDWmJKj5In636OScBrK2OQlJErcmREtVsGpxogqjImTaS1pFIJQns2wdo32sLKxADn72ai3zd/Yf+VVLFDI6q1mDQRVR2TJtJ63b0csHNKZ7RwtYI8rwhv/Xwa4buvoojzORFVGpMmoqrTFzsAInXUszLGb28HIHz3Vaw9Go/vD93GuYTH+GZYSzhZGokdHpFWibySisX7rpc7UWzi37e4mTQRVZ6oPU2HDx9G//794eLiAolEgoiIiGfuEx0djVatWkEmk6FRo0ZYt25djcdJ2sFQX4q5/ZviuxGtYCbTx8n4DPT75i8cuZkudmhEWuV/xxNwLSULtx7klNkKiwUYG+jBzcZU7DCJah1Re5pycnLg5+eHN998E4MGDXpm/Tt37qBfv34YP3481q9fj6ioKLz11ltwdnZGUFCQBiImbdC3mTN8nC0wYf1ZXE2WY+SaE5jWowkmv9gIepxzhggZOfkAgI/6eKGFq1WZ991tTWFpYqDhqIhqP1GTpj59+qBPnz5q11+5ciU8PDywePFiAIC3tzeOHDmCpUuXMmnSMe52ptg6sQPm7biMX08mYen+Gzhx5yEWv+YHZ0tjscMjElVGdsm4pfYNbctNmoioamrVQPCYmBgEBgaqlAUFBSEmJkakiEhMRgZ6CB/UHEte84OxQcnyK0FLD2PH+ftih0YkqtJZv205bomoWtWqpCklJQWOjo4qZY6OjpDL5Xjy5Em5++Tn50Mul6tsVLcMalUfu6Z2ht/fT9e98+s5TNt4DplPuOgv6Z7cgiLkFZYMALdm0kRUrWpV0lQV4eHhsLS0VG6urq5ih0Q1wMPOFFvGB2BKj8aQSoCI2PsIWnoYB65xTifSLaVTChjqS2FqqCdyNER1S61KmpycnJCaqvqfYGpqKiwsLGBsXP44lrCwMGRmZiq3pCSuY1ZXGehJEdqzCTaP7wA3WxOkyPPw5rrTmLbxnHK9LaK6TjkPk4khJBI+GEFUnWrVPE0BAQHYtWuXSllkZCQCAgIq3Ecmk0Emk9V0aKRFWrtZY8/ULlgSeR0/HrmDiNj7OBKXjvkv+6JvM2exwyOqdunZ+ThwLQ3FCgE3U7MBcB4mopogatKUnZ2NuLg45es7d+4gNjYWNjY2aNCgAcLCwnDv3j38/PPPAIDx48dj2bJl+OCDD/Dmm2/iwIED+O2337Bz506xToG0lLGhHmb280HfZs74YMsF3EzLxsT1Z9G7qRPmBzeFgzknxKS6Y3bEJey+lKJSZm/OH4tE1U3U23OnT59Gy5Yt0bJlSwBAaGgoWrZsiTlz5gAAkpOTkZiYqKzv4eGBnTt3IjIyEn5+fli8eDFWr17N6QaoQi0bWOPPKZ0w5cVG0JdKsOdyCnouOYxNpxKhUAhih0dULe6k5wAo6WUN9HZEH18nTOnRSOSoiOoeiSAIOvU/h1wuh6WlJTIzM2FhYSF2OKRBV+7L8cHv53HpXskTlG3drfFpcDN4OpmLHFndx+9dxarj2vgv2I9UeT7+fKcTfOtZVnOERHVPVb93tWogONHz8HGxQMTEjpjVzxsmhno4Ff8I/b75C5/+eQVZeZyegGonQRCUg785xQBRzWLSRDpFX0+Ktzo3xP7Qrujl44gihYDVR+6gx+JDiDh3DzrW8Up1QHZ+EQqLS/7d2pgwaSKqSUyaSCe5WBnjh5A2WDu6LdxtTZCWlY9pm2Ix5PvjuHg3U+zwiNRW2stkbKAHY87LRFSjmDSRTuvu6YC973bBe72awMhAipPxGei/7Aje3RSLe4/Ln2We6rbly5fD3d0dRkZG8Pf3x8mTJyusu2rVKnTu3BnW1tawtrZGYGDgU+vXhIel8zLx1hxRjWPSRDpPpq+HyS82RtT0bghu4QIA2HruHrovisbne65BzvFOOmPTpk0IDQ3F3LlzcfbsWfj5+SEoKAhpaWnl1o+OjsawYcNw8OBBxMTEwNXVFb169cK9e/c0FvMjJk1EGsOkiehv9ayM8dXQltg+uSP8PWxQUKTAiuhb6PZlNH46Fo/CYoXYIVINW7JkCcaOHYvRo0fDx8cHK1euhImJCdasWVNu/fXr12PixIlo0aIFvLy8sHr1aigUCkRFRWksZvY0EWkOkyai/2he3wobx7XH6pA2aGhvioycAszdfhk9lxzC5tNJKGLyVCcVFBTgzJkzCAwMVJZJpVIEBgYiJiZGrWPk5uaisLAQNjY2NRVmGexpItIcJk1E5ZBIJAj0ccTeaV3wSbAv7MwMEf8wF+9vuYAXFx/CplOJ7HmqY9LT01FcXAxHR0eVckdHR6SkpFSwl6oPP/wQLi4uKonXf+Xn50Mul6tsz0M53QCfnCOqcUyaiJ7CQE+Kke3dcOj97gjr4wVbU0MkZuTiw98vovuiaGw4kYiCIiZPBCxcuBAbN27E1q1bYWRU8TI94eHhsLS0VG6urq7P9bmlSZOtGZMmoprGpIlIDaYyfbzd9QX89WF3zOrnDTszGe4+eoIZWy+i25cH8b/jCcgvKhY7THoOdnZ20NPTQ2pqqkp5amoqnJycnrrvokWLsHDhQuzbtw/Nmzd/at2wsDBkZmYqt6SkpOeKmz1NRJrDpImoEkwM9fFW54b464PumPOSDxzMZbifmYfZEZfQ9YuSAeN5hUyeaiNDQ0O0bt1aZRB36aDugICACvf74osv8Mknn2DPnj1o06bNMz9HJpPBwsJCZXseGbkc00SkKUyaiKrA2FAPb3bywOEPumPegKZwsjBCijwPc7dfRpcvDmL1X7c5VUEtFBoailWrVuGnn37C1atXMWHCBOTk5GD06NEAgJCQEISFhSnrf/7555g9ezbWrFkDd3d3pKSkICUlBdnZ2RqLOYMDwYk0Rl/sAIhqMyMDPYzq4I6h7Vzx2+m7WHEwDvcz8/Dpzqv4av9NvNbGFaM7usPVxkTsUEkNQ4YMwYMHDzBnzhykpKSgRYsW2LNnj3JweGJiIqTSf35rrlixAgUFBXj11VdVjjN37lx8/PHHGomZSROR5kgEHVtsi6utU00qKFLgj7N3sfrIHcSllfQ2SCVAUFMnvNXZA60aWEMikYgcpebxe1exql6bvZdTcPm+HN9E3QQAnJ3dk4kTkZqq+r1jTxNRNTLUl2JouwYY0tYVh248wI9H7uCvm+nYfSkFuy+loHl9S7zu74aX/JxhYsivH1VNWlYexv9yBqU/eY0MpLA0NhA3KCIdwFabqAZIJBJ083RAN08HXE/Jwpojd7A19h4u3M3EB3cv4JOdV/BKq/oY7t8ATRzNxQ6XapmUzDwIAmAm08fAlvXQubEd9KS614NJpGlMmohqmKeTOT5/tTk+6O2JzWfuYsOJRCRm5GLdsXisOxaPdu42GNG+AXr7OkGmz1Xq6dlKxzE1sDHBJ8G+IkdDpDuYNBFpiK2ZDOO7voBxnRviSFw6fjmegKhraTgZn4GT8RmwNDZAcAsXDG7jiqYuFjo59onUw8HfROJg0kSkYVKpBF2a2KNLE3skZz7BplNJ2HQqCcmZefgpJgE/xSTAy8kcr7SqjwEtXOBoUfHs0qSbmDQRiYNJE5GInC2NMS2wCd55sTGOxKVj8+kk7LuSimspWfhs11Us2H0VHV6wRXCLeujt6wRzIw72JSZNRGJh0kSkBfSkEnRtYo+uTezxOLcAOy4kI+LcPZxJeISjcQ9xNO4hZkVcQqC3I15q7owuTexhKuPXV1c94izgRKJgq0ukZaxMDDGyvRtGtndDUkYutsXew9Zz93DrQQ52XkzGzovJkOlL0bmxHXr5OKGHtwNszWRih00akFdYjMJiBdLk+QAAayZNRBqlFcuoLF++HO7u7jAyMoK/vz9OnjxZYd1169ZBIpGobE9bUZyoNnO1McHkFxtjf2hX/PlOJ4zt7IEGNibIL1Jg/9U0fPD7BbT9bD9e+z4Gq/+6jaSMXLFDphpy6MYDNJ+3D80+3oeoa2kAAFsmTUQaJXpP06ZNmxAaGoqVK1fC398fX331FYKCgnD9+nU4ODiUu4+FhQWuX7+ufM2njKiuk0gk8K1nCd96lpjR1xvXU7Ow73Iq9l1JwaV7cpy8k4GTdzLw6c6r8Ha2QC8fR/Rq6ggfZz6FV1ccjUtHQZFC+drOTIZWDaxFjIhI94ieNC1ZsgRjx45VLoi5cuVK7Ny5E2vWrMFHH31U7j4SiQROTk6aDJNIa0gkEng5WcDLyQJTejTG3Ue5iLySin2XU3EyPgNXk+W4mizH11E3Ud/aGIHejujUyA7tX7CFGcdB1Vqlg7+n92yCcV0bwkAqhZQTWhJplKgtaEFBAc6cOaOyarhUKkVgYCBiYmIq3C87Oxtubm5QKBRo1aoVFixYgKZNm2oiZCKtU9/aBKM7emB0Rw88yilA1LU07LucgsM3H+DuoyfKSTT1pRK0crNG50Z26NzEHs3qWXIW6VqkNGmyN5dxElQikYiaNKWnp6O4uFi5gngpR0dHXLt2rdx9PD09sWbNGjRv3hyZmZlYtGgROnTogMuXL6N+/fpl6ufn5yM/P1/5Wi6XV+9JEGkRa1NDvNq6Pl5tXR9PCopx+OYDHL7xAH/dTEdiRq7yNt7iyBuwNDZAx0a26NTIHp0b28HVxkTs8OkpOM0AkfhqXV99QEAAAgIClK87dOgAb29vfP/99/jkk0/K1A8PD8e8efM0GSKRVjA21ENQUycENS25lZ3wMAd/3UzHkZvpOHorHZlPCrHrYgp2XUwBALjZmqCtuw3aedignbsN3GxNOB5KizBpIhKfqEmTnZ0d9PT0kJqaqlKempqq9pglAwMDtGzZEnFxceW+HxYWhtDQUOVruVwOV1fXqgdNVEu52ZrCzdYUr7d3Q1GxAufvZuLIzXT8dfMBziU9RsLDXCQ8zMWWM3cBlNwGKk2g2rrbwMvJnGNoRPSISROR6ERNmgwNDdG6dWtERUUhODgYAKBQKBAVFYXJkyerdYzi4mJcvHgRffv2Lfd9mUwGmYxz2BD9m76eFK3drNHazRpTAxsjK68QpxMe4eSdDJy6k4ELdzPxICsfOy8kY+eFZACAhZE+2vydQLVxt0azepYwMuDYGk3ILypGVn4RACZNRGIS/fZcaGgoRo0ahTZt2qBdu3b46quvkJOTo3yaLiQkBPXq1UN4eDgAYP78+Wjfvj0aNWqEx48f48svv0RCQgLeeustMU+DqFYzNzJAd08HdPcsmeYjr7AYsUmPcepOyWLCZxMeQZ5XhAPX0nDg7zmC9KUSeDmbo6WrNYa0dYVvPUsxT6FOe5xbCKBk5ngLLqVDJBrRk6YhQ4bgwYMHmDNnDlJSUtCiRQvs2bNHOTg8MTERUuk/c3A+evQIY8eORUpKCqytrdG6dWscO3YMPj4+Yp0CUZ1jZKCH9g1t0b6hLQCgqFiBK8kl80Gdis/A2cTHeJCVj0v35Lh0T45unvZMmmrQw+ySW3PWJga8RUokIokgCILYQWiSXC6HpaUlMjMzYWFhIXY4RLWSIAi4n5mHc4mPcC7xMSZ1b/TU20b83lVMnWuTJs/D9vP3AQBvdW6oyfCI6qSqtkmi9zQRUe0jkUhQz8oY9ayM8VJzF7HDqfMcLIyYLBFpAa1Ye46IiIhI2zFpIiIiIlIDkyYiIiIiNTBpIiIiIlIDkyYiIiIiNTBpIiIiIlKDzk05UDotlVwuFzkSIt1R+n3TsWnh1MI2iUjzqtom6VzSlJWVBQBctJdIBFlZWbC05Mzh/8Y2iUg8lW2TdG5GcIVCgfv378Pc3BwSScXLEcjlcri6uiIpKYkzGIPX4994LVSpcz0EQUBWVhZcXFxUlkUitklVwWuhitfjH+pei6q2STrX0ySVSlG/fn2161tYWOj8P8J/4/X4B6+FqmddD/YwlY9tUtXxWqji9fiHOteiKm0Sf/IRERERqYFJExEREZEamDRVQCaTYe7cuZDJZGKHohV4Pf7Ba6GK10MzeJ3/wWuhitfjHzV9LXRuIDgRERFRVbCniYiIiEgNTJqIiIiI1MCkiYiIiEgNTJoqsHz5cri7u8PIyAj+/v44efKk2CFVu8OHD6N///5wcXGBRCJBRESEyvuCIGDOnDlwdnaGsbExAgMDcfPmTZU6GRkZGDFiBCwsLGBlZYUxY8YgOztbg2dRPcLDw9G2bVuYm5vDwcEBwcHBuH79ukqdvLw8TJo0Cba2tjAzM8Mrr7yC1NRUlTqJiYno168fTExM4ODggPfffx9FRUWaPJVqsWLFCjRv3lw510lAQAB2796tfF+XroU20IX2CGCb9G9sk/6hVe2RQGVs3LhRMDQ0FNasWSNcvnxZGDt2rGBlZSWkpqaKHVq12rVrlzBz5kzhjz/+EAAIW7duVXl/4cKFgqWlpRARESGcP39eGDBggODh4SE8efJEWad3796Cn5+fcPz4ceGvv/4SGjVqJAwbNkzDZ/L8goKChLVr1wqXLl0SYmNjhb59+woNGjQQsrOzlXXGjx8vuLq6ClFRUcLp06eF9u3bCx06dFC+X1RUJPj6+gqBgYHCuXPnhF27dgl2dnZCWFiYGKf0XLZv3y7s3LlTuHHjhnD9+nVhxowZgoGBgXDp0iVBEHTrWohNV9ojQWCb9G9sk/6hTe0Rk6ZytGvXTpg0aZLydXFxseDi4iKEh4eLGFXN+m8DpVAoBCcnJ+HLL79Ulj1+/FiQyWTCr7/+KgiCIFy5ckUAIJw6dUpZZ/fu3YJEIhHu3bunsdhrQlpamgBAOHTokCAIJeduYGAgbN68WVnn6tWrAgAhJiZGEISSBl8qlQopKSnKOitWrBAsLCyE/Px8zZ5ADbC2thZWr17Na6FhutgeCQLbpP9im6RKrPaIt+f+o6CgAGfOnEFgYKCyTCqVIjAwEDExMSJGpll37txBSkqKynWwtLSEv7+/8jrExMTAysoKbdq0UdYJDAyEVCrFiRMnNB5zdcrMzAQA2NjYAADOnDmDwsJClevh5eWFBg0aqFyPZs2awdHRUVknKCgIcrkcly9f1mD01au4uBgbN25ETk4OAgICdPpaaBrbo3+wTWKbBIjfHunc2nPPkp6ejuLiYpWLCwCOjo64du2aSFFpXkpKCgCUex1K30tJSYGDg4PK+/r6+rCxsVHWqY0UCgWmTZuGjh07wtfXF0DJuRoaGsLKykql7n+vR3nXq/S92ubixYsICAhAXl4ezMzMsHXrVvj4+CA2NlbnroVY2B79g22SbrdJ2tIeMWki+o9Jkybh0qVLOHLkiNihiMrT0xOxsbHIzMzEli1bMGrUKBw6dEjssIh0Dtsk7WmPeHvuP+zs7KCnp1dm5H1qaiqcnJxEikrzSs/1adfByckJaWlpKu8XFRUhIyOj1l6ryZMn488//8TBgwdVVp53cnJCQUEBHj9+rFL/v9ejvOtV+l5tY2hoiEaNGqF169YIDw+Hn58fvv76a528FmJhe/QPtkm63SZpS3vEpOk/DA0N0bp1a0RFRSnLFAoFoqKiEBAQIGJkmuXh4QEnJyeV6yCXy3HixAnldQgICMDjx49x5swZZZ0DBw5AoVDA399f4zE/D0EQMHnyZGzduhUHDhyAh4eHyvutW7eGgYGByvW4fv06EhMTVa7HxYsXVRrtyMhIWFhYwMfHRzMnUoMUCgXy8/N5LTSI7dE/2CaxTfo30dqj6hjFXtds3LhRkMlkwrp164QrV64I48aNE6ysrFRG3tcFWVlZwrlz54Rz584JAIQlS5YI586dExISEgRBKHm818rKSti2bZtw4cIF4eWXXy738d6WLVsKJ06cEI4cOSI0bty4Vj7eO2HCBMHS0lKIjo4WkpOTlVtubq6yzvjx44UGDRoIBw4cEE6fPi0EBAQIAQEByvdLH2vt1auXEBsbK+zZs0ewt7evdY/3CoIgfPTRR8KhQ4eEO3fuCBcuXBA++ugjQSKRCPv27RMEQbeuhdh0pT0SBLZJ/8Y26R/a1B4xaarAt99+KzRo0EAwNDQU2rVrJxw/flzskKrdwYMHBQBltlGjRgmCUPKI7+zZswVHR0dBJpMJPXr0EK5fv65yjIcPHwrDhg0TzMzMBAsLC2H06NFCVlaWCGfzfMq7DgCEtWvXKus8efJEmDhxomBtbS2YmJgIAwcOFJKTk1WOEx8fL/Tp00cwNjYW7OzshOnTpwuFhYUaPpvn9+abbwpubm6CoaGhYG9vL/To0UPZQAmCbl0LbaAL7ZEgsE36N7ZJ/9Cm9kgiCIJQub4pIiIiIt3DMU1EREREamDSRERERKQGJk1EREREamDSRERERKQGJk1EREREamDSRERERKQGJk1EREREamDSRERERKQGJk1U50kkEkRERIgdBhERALZJtRmTJqpRb7zxBiQSSZmtd+/eYodGRDqIbRI9D32xA6C6r3fv3li7dq1KmUwmEykaItJ1bJOoqtjTRDVOJpPByclJZbO2tgZQ0k29YsUK9OnTB8bGxmjYsCG2bNmisv/Fixfx4osvwtjYGLa2thg3bhyys7NV6qxZswZNmzaFTCaDs7MzJk+erPJ+eno6Bg4cCBMTEzRu3Bjbt2+v2ZMmIq3FNomqikkTiW727Nl45ZVXcP78eYwYMQJDhw7F1atXAQA5OTkICgqCtbU1Tp06hc2bN2P//v0qDdCKFSswadIkjBs3DhcvXsT27dvRqFEjlc+YN28eXnvtNVy4cAF9+/bFiBEjkJGRodHzJKLagW0SVUggqkGjRo0S9PT0BFNTU5Xts88+EwRBEAAI48ePV9nH399fmDBhgiAIgvDDDz8I1tbWQnZ2tvL9nTt3ClKpVEhJSREEQRBcXFyEmTNnVhgDAGHWrFnK19nZ2QIAYffu3dV2nkRUO7BNoufBMU1U47p3744VK1aolNnY2Cj/HBAQoPJeQEAAYmNjAQBXr16Fn58fTE1Nle937NgRCoUC169fh0Qiwf3799GjR4+nxtC8eXPln01NTWFhYYG0tLSqnhIR1WJsk6iqmDRRjTM1NS3TNV1djI2N1apnYGCg8loikUChUNRESESk5dgmUVVxTBOJ7vjx42Vee3t7AwC8vb1x/vx55OTkKN8/evQopFIpPD09YW5uDnd3d0RFRWk0ZiKqu9gmUUXY00Q1Lj8/HykpKSpl+vr6sLOzAwBs3rwZbdq0QadOnbB+/XqcPHkSP/74IwBgxIgRmDt3LkaNGoWPP/4YDx48wDvvvIORI0fC0dERAPDxxx9j/PjxcHBwQJ8+fZCVlYWjR4/inXfe0eyJElGtwDaJqkzsQVVUt40aNUoAUGbz9PQUBKFkQOTy5cuFnj17CjKZTHB3dxc2bdqkcowLFy4I3bt3F4yMjAQbGxth7NixQlZWlkqdlStXCp6enoKBgYHg7OwsvPPOO8r3AAhbt25VqW9paSmsXbu2Rs6ZiLQX2yR6HhJBEAQxkjUioOQ+/tatWxEcHCx2KEREbJPoqTimiYiIiEgNTJqIiIiI1MDbc0RERERqYE8TERERkRqYNBERERGpgUkTERERkRqYNBERERGpgUkTERERkRqYNBERERGpgUkTERERkRqYNBERERGpgUkTERERkRr+D0f3bT43fnlpAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 600x300 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axes = plt.subplots(1, 2, figsize=(6, 3))\n",
    "\n",
    "axes[0].plot(history.history['loss'])\n",
    "axes[0].set_title('Training loss')\n",
    "axes[0].set_xlabel('Epoch')\n",
    "axes[0].set_ylabel('Loss')\n",
    "\n",
    "axes[1].plot(history.history['accuracy'])\n",
    "axes[1].set_title('Training accuracy')\n",
    "axes[1].set_xlabel('Epoch')\n",
    "axes[1].set_ylabel('Accuracy')\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25bcbaad",
   "metadata": {},
   "source": [
    "## 3. Model evaluation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "b947b44f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def predict_next(chars, model):\n",
    "    '''Predict the next character given a sequence.'''\n",
    "\n",
    "    indices = np.array([[char_to_idx[c] for c in chars]])\n",
    "    onehot = to_categorical(indices, num_classes=vocab_size)\n",
    "    pred = model.predict(onehot, verbose=0)\n",
    "    next_idx = np.argmax(pred)\n",
    "\n",
    "    return idx_to_char[next_idx]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "3519832f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Predictions:\n",
      " abc -> d\n",
      " def -> g\n",
      " mno -> p\n",
      " stu -> v\n",
      " wxy -> z\n"
     ]
    }
   ],
   "source": [
    "# Test predictions\n",
    "test_sequences = ['abc', 'def', 'mno', 'stu', 'wxy']\n",
    "\n",
    "print('Predictions:')\n",
    "\n",
    "for seq in test_sequences:\n",
    "    if len(seq) == seq_length and all(c in char_to_idx for c in seq):\n",
    "        pred = predict_next(seq, model)\n",
    "        print(f' {seq} -> {pred}')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
