{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "347df593",
   "metadata": {},
   "source": [
    "[تنزيل الدفتر العربي](notebook.ipynb){download=\"notebook-ar-chapter-2.ipynb\" data-noresolveinput=\"true\"}\n",
    "\n",
    "لتشغيل النسخة العربية، نزّل الملف وافتحه في Jupyter أو ارفعه إلى [Google Colab](https://colab.research.google.com/). ويمكنك أيضًا فتح [الدفتر الإنجليزي الأصلي في Colab](https://colab.research.google.com/github/BaytAlhikmah/hands-on-llms-for-swes/blob/main/chapters/2/notebook.ipynb).\n",
    "\n",
    "التعليمات هنا بالعربية. أبقينا البرامج وتعليقاتها ونصوص مخرجاتها كما هي لتظل التجارب مطابقة للنسخة الأصلية."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ebznm1h29wm",
   "metadata": {},
   "source": [
    "# الفصل 2 — أساسيات نظرية المعلومات\n",
    "\n",
    "**اقرأ هذا الدفتر بالتوازي مع [الدرس](lesson.qmd)**؛ فهو يحتوي على التمارين المشار إليها فيه.\n",
    "\n",
    "رأيت في الفصل الأول أن نماذج اللغة تتنبّأ بالرمز التالي. لكن وراء كل تنبؤ أسئلة أعمق: ما مقدار المعلومات في اللغة؟ وبأي كفاءة يمكن ترميزها؟ وماذا يحدث إذا كانت افتراضاتنا عن البيانات خاطئة؟\n",
    "\n",
    "نجيب عن هذه الأسئلة باستخدام نظرية المعلومات لشانون: نقيس أولًا صعوبة التنبؤ (الإنتروبيا)، ثم عدم كفاءة الترميز (الإنتروبيا المتقاطعة).\n",
    "\n",
    "---\n",
    "\n",
    "## الجزء الأول: فهم الإنتروبيا\n",
    "\n",
    "_يقابل الأقسام من 0 إلى 3 في الدرس._\n",
    "\n",
    "قبل قياس كفاءة الترميز، علينا فهم **الإنتروبيا**: مقياس شانون لصعوبة التنبؤ والحد النظري للضغط."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "41ab7e89",
   "metadata": {},
   "source": [
    "### الإعداد"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2190a373",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install the course package\n",
    "!pip install -q git+https://github.com/BaytAlhikmah/hands-on-llms-for-swes.git#subdirectory=pkg\n",
    "\n",
    "print('✓ Setup complete!')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eif96rg4fol",
   "metadata": {},
   "source": [
    "### التمرين 1: شجرة القرار الثنائية (الحالة المنتظمة)\n",
    "\n",
    "_القسم 1 من الدرس: لعبة التخمين مع 8 خيارات متساوية الاحتمال._\n",
    "\n",
    "شاهد كيف يعمل البحث الثنائي عندما تتساوى احتمالات النتائج. مع 8 خيارات، تحتاج دائمًا إلى $\\log_2(8) = 3$ أسئلة بالضبط."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "h5wdacf3hqb",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "from alhikmah_llms import Chapter2\n",
    "\n",
    "print(\"Expected: A balanced binary tree with 8 leaves at depth 3\")\n",
    "print(\"Each path from root to leaf represents a sequence of yes/no questions\\n\")\n",
    "\n",
    "# Visualize the uniform case: 8 equally likely outcomes\n",
    "Chapter2.draw_uniform_tree()\n",
    "plt.show()\n",
    "\n",
    "print(\"\\nEntropy = log₂(8) = 3.0 bits\")\n",
    "print(\"Every path from root to leaf takes exactly 3 questions.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2tvcr72qv8o",
   "metadata": {},
   "source": [
    "### التمرين 2: شجرة القرار الثنائية (الحالة المنحازة)\n",
    "\n",
    "_القسم 1 من الدرس: لعبة التخمين مع احتمالات منحازة._\n",
    "\n",
    "عندما لا تتساوى احتمالات النتائج، يمكنك تحسين استراتيجيتك: اختبر النتيجة الأرجح أولًا.\n",
    "\n",
    "لاحظ أن القيمة 2.4035 التي تطبعها الشفرة هي الإنتروبيا بالبت، أي الحد النظري للمتوسط. أما أفضل شجرة أسئلة لجولة واحدة في هذه اللعبة فمتوسطها نحو 2.4286 سؤال، كما يوضّح الدرس."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "r6wokmanwwb",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Expected: A skewed binary tree where the most frequent outcome\")\n",
    "print(\"gets checked first (shortest path = 1 question)\\n\")\n",
    "\n",
    "# Visualize the biased case: 50% on option 1, rest split among 2-8\n",
    "Chapter2.draw_biased_tree()\n",
    "plt.show()\n",
    "\n",
    "print(\"\\nEntropy ≈ 2.4035 bits\")\n",
    "print(\"50% of the time, you're done in 1 question!\")\n",
    "print(\"Average: 0.5 × 1 + 0.5 × 3.807 = 2.4035 questions\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "jz359afusrf",
   "metadata": {},
   "source": [
    "### التمرين 3: عرض التوزيعات الاحتمالية\n",
    "\n",
    "_القسم 1 من الدرس: علاقة الإنتروبيا بمدى «انتشار» الاحتمالات._\n",
    "\n",
    "كلما تركّز الاحتمال في عدد أقل من النتائج، انخفضت الإنتروبيا."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "wzyol0oa5jl",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Expected: Two bar charts comparing uniform vs biased distributions\")\n",
    "print(\"The uniform distribution should appear 'flat', the biased 'spiky'\\n\")\n",
    "\n",
    "# Compare the distributions\n",
    "Chapter2.plot_distributions()\n",
    "plt.show()\n",
    "\n",
    "print(\"\\nKey Observation:\")\n",
    "print(\"   Uniform (flat) = maximum entropy = maximum unpredictability\")\n",
    "print(\"   Biased (spiky) = lower entropy = more predictable\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "x6bsg8u9px",
   "metadata": {},
   "source": [
    "### التمرين 4: حساب الإنتروبيا لتوزيعات مختلفة\n",
    "\n",
    "_القسم 2 من الدرس: اشتقاق صيغة الإنتروبيا من المبادئ الأولى._\n",
    "\n",
    "تحقّق من أن الصيغة $H = \\sum p(x) \\log_2\\left(\\frac{1}{p(x)}\\right)$ توافق فهمنا الحدسي."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "z4muu37prro",
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "\n",
    "def entropy(probs: list[float]) -> float:\n",
    "    \"\"\"Average number of yes/no questions (bits) to guess the outcome.\"\"\"\n",
    "    return sum(p * math.log2(1/p) for p in probs if p > 0)\n",
    "\n",
    "# Test the formula\n",
    "uniform_8 = [1/8] * 8\n",
    "biased = [0.5] + [0.5/7] * 7\n",
    "certain = [1.0] + [0.0] * 7\n",
    "\n",
    "print(f\"Uniform (all equal):     {entropy(uniform_8):.4f} bits  (= log₂(8))\")\n",
    "print(f\"Biased (50% on one):     {entropy(biased):.4f} bits\")\n",
    "print(f\"Certain (100% on one):   {entropy(certain):.4f} bits\")\n",
    "print()\n",
    "print(\"Lower entropy = more predictable = fewer questions needed\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "59bdb2xwcb",
   "metadata": {},
   "source": [
    "### التمرين 5: تنفيذ دالة الإنتروبيا واختبارها\n",
    "\n",
    "_القسم 2 من الدرس: فهم الصيغتين المتكافئتين._\n",
    "\n",
    "يمكن كتابة الصيغة على صورة $-\\sum p \\log_2(p)$ أو $\\sum p \\log_2\\left(\\frac{1}{p}\\right)$. لماذا تتكافآن؟"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "erbbeadrs5n",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Two equivalent forms\n",
    "def entropy_form1(probs: list[float]) -> float:\n",
    "    \"\"\"Form 1: Σ p(x) × log₂(1/p(x))\"\"\"\n",
    "    return sum(p * math.log2(1/p) for p in probs if p > 0)\n",
    "\n",
    "def entropy_form2(probs: list[float]) -> float:\n",
    "    \"\"\"Form 2: -Σ p(x) × log₂(p(x))\"\"\"\n",
    "    return -sum(p * math.log2(p) for p in probs if p > 0)\n",
    "\n",
    "# Verify they're the same\n",
    "test_probs = [0.6, 0.2, 0.1, 0.1]\n",
    "h1 = entropy_form1(test_probs)\n",
    "h2 = entropy_form2(test_probs)\n",
    "\n",
    "print(f\"Form 1: {h1:.6f} bits\")\n",
    "print(f\"Form 2: {h2:.6f} bits\")\n",
    "print(f\"Difference: {abs(h1 - h2):.10f}\")\n",
    "print()\n",
    "print(\"Why equivalent? Because log₂(1/p) = log₂(1) - log₂(p) = 0 - log₂(p) = -log₂(p)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "i926fzcab",
   "metadata": {},
   "source": [
    "### التمرين 6: عرض الضغط بصريًا\n",
    "\n",
    "_القسم 3ب من الدرس: الإنتروبيا بوصفها الحد الأمثل لحجم البيانات المضغوطة._\n",
    "\n",
    "شاهد كيف يوفّر الترميز المبني على الإنتروبيا مساحةً بتخصيص شفرات قصيرة للرموز المتكررة."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10xeuje9ycbi",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Expected: Visualization showing how frequent symbols get short codes\")\n",
    "print(\"and rare symbols get long codes in optimal compression\\n\")\n",
    "\n",
    "Chapter2.plot_compression_example()\n",
    "plt.show()\n",
    "\n",
    "print(\"\\nKey Insight:\")\n",
    "print(\"   Entropy = theoretical compression limit\")\n",
    "print(\"   Frequent symbols get short codes, rare symbols get long codes\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6co4g8ytyvg",
   "metadata": {},
   "source": [
    "### التمرين 7: منحنى المفاجأة\n",
    "\n",
    "_القسم 3ج من الدرس: الإنتروبيا بوصفها متوسط المفاجأة._\n",
    "\n",
    "شاهد كيف يرتبط مقدار المفاجأة $-\\log_2(p)$ بالاحتمال."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "qcfrxmicyjf",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Expected: A curve showing surprise decreases as probability increases\")\n",
    "print(\"High probability events → low surprise, low probability → high surprise\\n\")\n",
    "\n",
    "Chapter2.plot_surprise_curve()\n",
    "plt.show()\n",
    "\n",
    "print(\"\\nKey Insight:\")\n",
    "print(\"   High probability → low surprise (you expected it)\")\n",
    "print(\"   Low probability → high surprise (you didn't see that coming!)\")\n",
    "print(\"   Entropy = expected surprise across all possible outcomes\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "nsaoz2bq1ci",
   "metadata": {},
   "source": [
    "### التمرين 8: استكشاف الإنتروبيا عبر التوزيعات\n",
    "\n",
    "_القسم 3 من الدرس: رسوم تفاعلية._\n",
    "\n",
    "#### التمرين 8أ: دالة الإنتروبيا الثنائية\n",
    "\n",
    "منحنى أساسي في نظرية المعلومات: إنتروبيا رمية عملة يكون احتمال إحدى نتيجتيها $p$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "r01p4cijuy",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Expected: A symmetric bell-shaped curve peaking at p=0.5\")\n",
    "print(\"showing entropy as a function of probability\\n\")\n",
    "\n",
    "Chapter2.plot_binary_entropy()\n",
    "plt.show()\n",
    "\n",
    "print(\"\\nKey Observation:\")\n",
    "print(\"   Maximum entropy at p=0.5 (fair coin flip)\")\n",
    "print(\"   Zero entropy at p=0 or p=1 (certain outcome)\")\n",
    "print(\"   Symmetric curve — this is the foundation of all entropy calculations!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "hovlttnu658",
   "metadata": {},
   "source": [
    "#### التمرين 8ب: الإنتروبيا لثلاث نتائج\n",
    "\n",
    "يعرض الرسم الإنتروبيا لجميع التوزيعات الاحتمالية الممكنة ذات النتائج الثلاث."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "m2t51yoainl",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Expected: A 3D surface plot showing entropy over the probability simplex\")\n",
    "print(\"Peak at center (uniform), valleys at corners (certain outcomes)\")\n",
    "print(\"This is an interactive plot - click and drag to rotate!\\n\")\n",
    "\n",
    "Chapter2.plot_ternary_entropy_3d_plotly().show()\n",
    "\n",
    "print(\"\\nKey Observation:\")\n",
    "print(\"   Click and drag to rotate the 3D surface!\")\n",
    "print(\"   Peak at center (uniform 1/3, 1/3, 1/3) = maximum entropy ≈ 1.585 bits\")\n",
    "print(\"   Valleys at corners (certain outcome) = zero entropy\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "runygtj591",
   "metadata": {},
   "source": [
    "#### التمرين 8ج: الخصائص الرياضية للإنتروبيا\n",
    "\n",
    "بعد أن رأيت الإنتروبيا عمليًا، لنصغ أبرز خصائصها رياضيًا. تساعد هذه الخصائص على فهم سبب ملاءمتها لقياس عدم اليقين.\n",
    "\n",
    "**الخاصية الأولى: عدم السلبية**\n",
    "\n",
    "$$H(X) \\geq 0$$\n",
    "\n",
    "الإنتروبيا غير سالبة دائمًا، وتساوي صفرًا فقط عندما يكون احتمال إحدى النتائج 1 (يقين تام).\n",
    "\n",
    "**الخاصية الثانية: القيمة العظمى للإنتروبيا**\n",
    "\n",
    "$$H(X) \\leq \\log_2(n)$$\n",
    "\n",
    "عند وجود $n$ نتائج ممكنة، تبلغ الإنتروبيا أقصاها عندما تتساوى احتمالات النتائج (التوزيع المنتظم). والقيمة العظمى هي $\\log_2(n)$ بت.\n",
    "\n",
    "**الخاصية الثالثة: الاستمرارية**\n",
    "\n",
    "الإنتروبيا $H$ دالة مستمرة في التوزيع الاحتمالي؛ فتغيّرات صغيرة في الاحتمالات تؤدي إلى تغيّرات صغيرة في الإنتروبيا.\n",
    "\n",
    "**الخاصية الرابعة: التناظر**\n",
    "\n",
    "لا تتغيّر $H$ بتغيير ترتيب النتائج. الترتيب لا يهم؛ الاحتمالات وحدها هي المهمة.\n",
    "\n",
    "**الخاصية الخامسة: قابلية الجمع (للمتغيّرات المستقلة)**\n",
    "\n",
    "$$H(X,Y) = H(X) + H(Y)$$\n",
    "\n",
    "إذا كان $X$ و$Y$ مستقلّين، فإن الإنتروبيا المشتركة تساوي مجموع إنتروبيا كل منهما.\n",
    "\n",
    "**الخاصية السادسة: قاعدة السلسلة**\n",
    "\n",
    "$$H(X,Y) = H(X) + H(Y|X)$$\n",
    "\n",
    "الإنتروبيا المشتركة تساوي إنتروبيا $X$ مضافًا إليها الإنتروبيا الشرطية لـ$Y$ عند معرفة $X$.\n",
    "\n",
    "**الخاصية السابعة: الاشتراط يقلّل الإنتروبيا**\n",
    "\n",
    "$$H(Y|X) \\leq H(Y)$$\n",
    "\n",
    "معرفة $X$ تقلّل عدم اليقين حول $Y$ أو تبقيه كما هو في المتوسط. وتتحقق المساواة عندما يكون $X$ و$Y$ مستقلّين.\n",
    "\n",
    "تعبّر هذه الخصائص عن حقائق أساسية حول المعلومات وعدم اليقين."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "s2lrudslbec",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Demonstrate the properties with examples\n",
    "\n",
    "# Property 1: Non-negativity\n",
    "print(\"Property 1: Non-negativity\")\n",
    "print(\"-\" * 50)\n",
    "certain = [1.0, 0.0, 0.0]\n",
    "uniform = [1/3, 1/3, 1/3]\n",
    "biased = [0.7, 0.2, 0.1]\n",
    "\n",
    "print(f\"  Certain outcome:  H = {entropy(certain):.4f} bits  (minimum)\")\n",
    "print(f\"  Uniform:          H = {entropy(uniform):.4f} bits\")\n",
    "print(f\"  Biased:           H = {entropy(biased):.4f} bits\")\n",
    "print(f\"  All ≥ 0 ✓\\n\")\n",
    "\n",
    "# Property 2: Maximum Entropy\n",
    "print(\"Property 2: Maximum Entropy\")\n",
    "print(\"-\" * 50)\n",
    "n = 8\n",
    "uniform_8 = [1/8] * 8\n",
    "max_entropy = math.log2(n)\n",
    "actual_entropy = entropy(uniform_8)\n",
    "print(f\"  For n={n} outcomes:\")\n",
    "print(f\"  Maximum possible:  log₂({n}) = {max_entropy:.4f} bits\")\n",
    "print(f\"  Uniform achieves:  H = {actual_entropy:.4f} bits\")\n",
    "print(f\"  Any other distribution will have H < {max_entropy:.4f}\")\n",
    "\n",
    "# Test with non-uniform\n",
    "non_uniform = [0.5, 0.1, 0.1, 0.1, 0.1, 0.05, 0.03, 0.02]\n",
    "print(f\"  Non-uniform:       H = {entropy(non_uniform):.4f} bits < {max_entropy:.4f} ✓\\n\")\n",
    "\n",
    "# Property 4: Symmetry\n",
    "print(\"Property 4: Symmetry\")\n",
    "print(\"-\" * 50)\n",
    "dist1 = [0.5, 0.3, 0.2]\n",
    "dist2 = [0.2, 0.5, 0.3]  # Same probabilities, different order\n",
    "dist3 = [0.3, 0.2, 0.5]  # Another permutation\n",
    "print(f\"  [0.5, 0.3, 0.2] → H = {entropy(dist1):.6f} bits\")\n",
    "print(f\"  [0.2, 0.5, 0.3] → H = {entropy(dist2):.6f} bits\")\n",
    "print(f\"  [0.3, 0.2, 0.5] → H = {entropy(dist3):.6f} bits\")\n",
    "print(f\"  Order doesn't matter, only the probabilities ✓\\n\")\n",
    "\n",
    "# Property 5: Additivity (independent events)\n",
    "print(\"Property 5: Additivity for Independent Events\")\n",
    "print(\"-\" * 50)\n",
    "# Two independent coin flips\n",
    "coin1 = [0.5, 0.5]  # Fair coin\n",
    "coin2 = [0.6, 0.4]  # Biased coin\n",
    "\n",
    "H_coin1 = entropy(coin1)\n",
    "H_coin2 = entropy(coin2)\n",
    "\n",
    "# Joint distribution for independent events: P(X,Y) = P(X) * P(Y)\n",
    "joint = [coin1[i] * coin2[j] for i in range(2) for j in range(2)]\n",
    "H_joint = entropy(joint)\n",
    "\n",
    "print(f\"  Coin 1:            H = {H_coin1:.4f} bits\")\n",
    "print(f\"  Coin 2:            H = {H_coin2:.4f} bits\")\n",
    "print(f\"  Sum:               H₁ + H₂ = {H_coin1 + H_coin2:.4f} bits\")\n",
    "print(f\"  Joint (independent): H(X,Y) = {H_joint:.4f} bits\")\n",
    "print(f\"  Difference: {abs(H_joint - (H_coin1 + H_coin2)):.6f} (should be ~0) ✓\\n\")\n",
    "\n",
    "# Property 7: Conditioning Reduces Entropy\n",
    "print(\"Property 7: Conditioning Reduces Entropy\")\n",
    "print(\"-\" * 50)\n",
    "print(\"  This is the key property for prediction!\")\n",
    "print(\"  Knowing context can only reduce uncertainty about what comes next.\")\n",
    "print(\"  We'll apply this in Chapter 3 with real sequence data:\\n\")\n",
    "print(\"  - H(next move) without knowing previous = higher entropy\")\n",
    "print(\"  - H(next move | previous move) = lower entropy\")\n",
    "print(\"  - The reduction tells us how much context helps predict the next outcome\\n\")\n",
    "\n",
    "print(\"Key Takeaway:\")\n",
    "print(\"   These properties explain why entropy is the 'right' way to measure uncertainty.\")\n",
    "print(\"   Any other measure that satisfies these axioms must be equivalent to Shannon entropy.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c4682c4",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "85b90932",
   "metadata": {},
   "source": [
    "## الجزء الثاني: الإنتروبيا المتقاطعة — الترميز بافتراضات غير مطابقة\n",
    "\n",
    "_يقابل القسم 4 من الدرس._\n",
    "\n",
    "الهدف: عرض أربع أشجار ترميز مختلفة للبيانات نفسها لتوضيح الإنتروبيا المتقاطعة.\n",
    "\n",
    "**البيانات:** الملف `AAAAAABBCD` (10 أحرف).\n",
    "\n",
    "- التوزيع الحقيقي $P$: ‏A=60%، وB=20%، وC=10%، وD=10%.\n",
    "\n",
    "**أربع حالات:**\n",
    "\n",
    "1. **جدول ترميز أمثل**: مصمّم لـ$P$ ‏(A=60%، وB=20%، وC=10%، وD=10%).\n",
    "2. **جدول ترميز منتظم**: مصمّم لـ`Q_uniform` ‏(A=25%، وB=25%، وC=25%، وD=25%).\n",
    "3. **جدول ذو انحياز خاطئ**: مصمّم لـ`Q_biased` ‏(C=50%، وA=25%، وB=12.5%، وD=12.5%).\n",
    "4. **جدول بأولويات معكوسة**: مصمّم لـ`Q_reversed` ‏(D=50%، وC=25%، وA=12.5%، وB=12.5%).\n",
    "\n",
    "سنحسب متوسط عدد البتات لكل حرف عند ترميز بيانات $P$ باستخدام كل جدول."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6bcc19f",
   "metadata": {},
   "source": [
    "### التمرين 9: الشجرة الأولى — الترميز الأمثل للتوزيع الحقيقي P\n",
    "\n",
    "**التوزيع الحقيقي:** $P = \\{A: 60\\%, B: 20\\%, C: 10\\%, D: 10\\%\\}$.\n",
    "\n",
    "**ترميز هوفمان:**\n",
    "\n",
    "- الحرف A (60%): `0` (بت واحد).\n",
    "- الحرف B (20%): `10` (بتّان).\n",
    "- الحرف C (10%): `110` (3 بتات).\n",
    "- الحرف D (10%): `111` (3 بتات).\n",
    "\n",
    "**متوسط البتات:** $0.6 \\times 1 + 0.2 \\times 2 + 0.1 \\times 3 + 0.1 \\times 3 = 1.6$ بت.\n",
    "\n",
    "هذا قريب من الإنتروبيا النظرية $H(P) \\approx 1.57$ بت. يستخدم ترميز هوفمان أطوالًا صحيحة من البتات؛ لذلك يقترب في هذا المثال من الإنتروبيا دون أن يساويها تمامًا."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bf82c60e",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Expected: A Huffman tree optimized for the true distribution P\")\n",
    "print(\"Symbol A (most frequent) gets the shortest path (1 bit)\\n\")\n",
    "\n",
    "Chapter2.draw_optimal_tree()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eec42c42",
   "metadata": {},
   "source": [
    "### التمرين 10: الشجرة الثانية — جدول ترميز منتظم\n",
    "\n",
    "**التوزيع المفترض:** $Q_{\\text{uniform}} = \\{A: 25\\%, B: 25\\%, C: 25\\%, D: 25\\%\\}$.\n",
    "\n",
    "في التوزيع المنتظم، تحصل جميع الرموز على طول متساوٍ، أي بتّين لكل رمز:\n",
    "\n",
    "- A: ‏`00`\n",
    "- B: ‏`01`\n",
    "- C: ‏`10`\n",
    "- D: ‏`11`\n",
    "\n",
    "إذا استخدمناه لترميز البيانات الحقيقية $P$:\n",
    "\n",
    "**متوسط البتات:** $0.6 \\times 2 + 0.2 \\times 2 + 0.1 \\times 2 + 0.1 \\times 2 = 2.0$ بت.\n",
    "\n",
    "هذه هي الإنتروبيا المتقاطعة $H(P, Q_{\\text{uniform}})$!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "236ac18d",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Expected: A balanced tree where all symbols get equal 2-bit codes\")\n",
    "print(\"This wastes bits because it ignores that A is much more frequent\\n\")\n",
    "\n",
    "Chapter2.draw_uniform_tree()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fef4c8d1",
   "metadata": {},
   "source": [
    "### التمرين 11: الشجرة الثالثة — جدول ذو انحياز خاطئ\n",
    "\n",
    "**التوزيع المفترض:** $Q_{\\text{biased}} = \\{A: 25\\%, B: 12.5\\%, C: 50\\%, D: 12.5\\%\\}$.\n",
    "\n",
    "ترميز هوفمان المصمّم لـ$Q_{\\text{biased}}$:\n",
    "\n",
    "- الحرف C (50%): `0` (بت واحد).\n",
    "- الحرف A (25%): `10` (بتّان).\n",
    "- الحرف B (12.5%): `110` (3 بتات).\n",
    "- الحرف D (12.5%): `111` (3 بتات).\n",
    "\n",
    "لكننا نرمّز بيانات $P$، حيث A=60%، وB=20%، وC=10%!\n",
    "\n",
    "**متوسط البتات:** $0.6 \\times 2 + 0.2 \\times 3 + 0.1 \\times 1 + 0.1 \\times 3 = 2.2$ بت.\n",
    "\n",
    "هذه هي $H(P, Q_{\\text{biased}})$: إنتروبيا متقاطعة أسوأ!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "43f7b549",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Expected: A tree that prioritizes C (wrong assumption!)\")\n",
    "print(\"This wastes even more bits because C is rare in the actual data\\n\")\n",
    "\n",
    "Chapter2.draw_wrong_biased_tree()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c156d2a9",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "### 🤔 تحدٍّ: هل يمكنك جعل الترميز أسوأ؟\n",
    "\n",
    "رأينا ثلاثة جداول ترميز:\n",
    "\n",
    "- **هوفمان الأمثل**: 1.6 بت (قريب من الحد النظري).\n",
    "- **المنتظم**: 2.0 بت (هدر إضافي 0.4 بت).\n",
    "- **ذو الانحياز الخاطئ**: 2.2 بت (هدر إضافي 0.6 بت).\n",
    "\n",
    "**السؤال:** هل يمكنك تصميم شجرة أو ترميز أسوأ؟\n",
    "\n",
    "فكّر: ما **أسوأ طريقة ممكنة** لتخصيص الشفرات لرموزنا؟\n",
    "\n",
    "<details>\n",
    "<summary>💡 تلميح (انقر لإظهاره)</summary>\n",
    "\n",
    "ماذا لو عكسنا أولويات الترميز الأمثل تمامًا، وأعطينا:\n",
    "\n",
    "- **أقصر الشفرات** للرموز **الأقل تكرارًا**؟\n",
    "- **أطول الشفرات** للرموز **الأكثر تكرارًا**؟\n",
    "\n",
    "هل ستكون هذه أسوأ حالة؟\n",
    "</details>\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e32f3621",
   "metadata": {},
   "source": [
    "### التمرين 12: الشجرة الرابعة — جدول بأولويات معكوسة (أسوأ حالة لهذه الأطوال)\n",
    "\n",
    "**نعم، يمكن جعل الترميز أسوأ!** إليك جدولًا سيئًا جدًا لـ$P$ باستخدام أطوال الشفرات (1، 2، 3، 3).\n",
    "\n",
    "**التوزيع المفترض:** $Q_{\\text{reversed}} = \\{A: 12.5\\%, B: 12.5\\%, C: 25\\%, D: 50\\%\\}$.\n",
    "\n",
    "نعكس أولويات الشجرة المثلى تمامًا:\n",
    "\n",
    "- **D (10% فعليًا)**: بت واحد؛ أقصر شفرة لأحد **أقل الرموز تكرارًا**!\n",
    "- **C (10% فعليًا)**: بتّان.\n",
    "- **A (60% فعليًا)**: 3 بتات؛ أطول شفرة للرمز **الأكثر تكرارًا**! 💀\n",
    "- **B (20% فعليًا)**: 3 بتات.\n",
    "\n",
    "**متوسط البتات:** $0.6 \\times 3 + 0.2 \\times 3 + 0.1 \\times 2 + 0.1 \\times 1 = 2.7$ بت.\n",
    "\n",
    "هذه هي $H(P, Q_{\\text{reversed}})$، وهي إنتروبيا متقاطعة سيئة جدًا.\n",
    "\n",
    "**الهدر:** $2.7 - 1.6 = 1.1$ بت لكل حرف، أي زيادة بنحو 69% عن هوفمان الأمثل!\n",
    "\n",
    "**ملاحظة:** هذه **أسوأ حالة عند تثبيت أطوال الشفرات (1، 2، 3، 3)**. فهل يمكن تجاوزها؟\n",
    "\n",
    "**نعم! لا يوجد حد أعلى للإنتروبيا المتقاطعة.** لفهم ذلك، ننظر إلى التوزيع $Q$ نفسه:\n",
    "\n",
    "$$H(P,Q) = -\\sum_{x} P(x) \\log_2 Q(x)$$\n",
    "\n",
    "- إذا أسند $Q$ احتمالات شديدة الانخفاض لرموز يُرجّحها $P$، أمكن أن تتجه $H(P,Q)$ إلى ما لا نهاية.\n",
    "- مثال: إذا كان $Q = \\{A: 0.01, B: 0.33, C: 0.33, D: 0.33\\}$، فإن $H(P,Q) \\approx 4.63$ بت.\n",
    "- عندما $Q(A) \\to 0$ بينما $P(A) = 0.6$، نحصل على $H(P,Q) \\to \\infty$.\n",
    "\n",
    "الفكرة هي أن بإمكانك بناء توزيعات $Q$ سيئة بلا حد؛ فالمسألة تتعلّق بالاحتمالات المفترضة، وليست مجرد اختيار أطوال شفرات طويلة عشوائيًا."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2c43fa3c",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Expected: The WORST tree - gives shortest codes to rarest symbols!\")\n",
    "print(\"This is the opposite of optimal coding\\n\")\n",
    "\n",
    "Chapter2.draw_worst_case_tree()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60502542",
   "metadata": {},
   "source": [
    "### التمرين 13: مقارنة الأشجار الأربع جنبًا إلى جنب\n",
    "\n",
    "من الترميز المصمّم للتوزيع الحقيقي ($Q=P$) إلى الترميز ذي الأولويات المعكوسة."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "862f814f",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Expected: Four trees side-by-side showing progression from\")\n",
    "print(\"optimal (1.6 bits) to worst-case reversed (2.7 bits)\\n\")\n",
    "\n",
    "Chapter2.draw_all_four_comparison()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "161b4044",
   "metadata": {},
   "source": [
    "### التمرين 14: حساب الإنتروبيا والإنتروبيا المتقاطعة عدديًا\n",
    "\n",
    "في هذه الأمثلة، جميع احتمالات $Q$ موجبة. عند تجربة توزيعات أخرى، تذكّر أن وجود $P(x)>0$ مع $Q(x)=0$ يجعل الإنتروبيا المتقاطعة لا نهائية؛ الدالة التعليمية أدناه تتخطّى قيم $Q(x)=0$، لذا لا تستخدمها كما هي لحساب تلك الحالة."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5f878593",
   "metadata": {},
   "outputs": [],
   "source": [
    "# True distribution P\n",
    "P = {'A': 0.6, 'B': 0.2, 'C': 0.1, 'D': 0.1}\n",
    "\n",
    "# Codebook 1: Optimal for P\n",
    "codes_optimal = {'A': '0', 'B': '10', 'C': '110', 'D': '111'}\n",
    "\n",
    "# Codebook 2: Uniform\n",
    "codes_uniform = {'A': '00', 'B': '01', 'C': '10', 'D': '11'}\n",
    "\n",
    "# Codebook 3: Wrong-biased (C gets shortest code, designed for Q where C is most frequent)\n",
    "codes_biased = {'C': '0', 'A': '10', 'B': '110', 'D': '111'}\n",
    "\n",
    "# Codebook 4: Reversed-priority (complete reverse of optimal)\n",
    "codes_reversed = {'D': '0', 'C': '10', 'A': '110', 'B': '111'}\n",
    "\n",
    "def average_bits(P_dist, codebook):\n",
    "    \"\"\"Calculate average bits per character when encoding P with codebook.\"\"\"\n",
    "    return sum(P_dist[char] * len(codebook[char]) for char in P_dist)\n",
    "\n",
    "def entropy(P_dist):\n",
    "    \"\"\"Calculate Shannon entropy H(P).\"\"\"\n",
    "    return -sum(p * math.log2(p) for p in P_dist.values() if p > 0)\n",
    "\n",
    "def cross_entropy(P_dist, Q_dist):\n",
    "    \"\"\"Calculate cross-entropy H(P,Q) = -∑ P(x) log₂ Q(x)\"\"\"\n",
    "    return -sum(P_dist[x] * math.log2(Q_dist[x]) for x in P_dist if Q_dist[x] > 0)\n",
    "\n",
    "H_P = entropy(P)\n",
    "avg_optimal = average_bits(P, codes_optimal)\n",
    "avg_uniform = average_bits(P, codes_uniform)\n",
    "avg_biased = average_bits(P, codes_biased)\n",
    "avg_reversed = average_bits(P, codes_reversed)\n",
    "\n",
    "print(\"=\"*70)\n",
    "print(\"ENTROPY AND CROSS-ENTROPY COMPARISON\")\n",
    "print(\"=\"*70)\n",
    "print(f\"\\nTrue distribution P: A=60%, B=20%, C=10%, D=10%\")\n",
    "print(f\"\\nH(P) = Entropy of P = {H_P:.4f} bits\")\n",
    "print(f\"  → Theoretical minimum bits per character\\n\")\n",
    "\n",
    "print(f\"1. Optimal codebook (designed for P):\")\n",
    "print(f\"   Average bits = {avg_optimal:.4f} bits\")\n",
    "print(f\"   Wasted = {avg_optimal - H_P:.4f} bits (≈ 0, very close to optimal!)\\n\")\n",
    "\n",
    "print(f\"2. Uniform codebook (designed for Q_uniform):\")\n",
    "print(f\"   H(P, Q_uniform) = {avg_uniform:.4f} bits\")\n",
    "print(f\"   Wasted = {avg_uniform - H_P:.4f} bits (+{((avg_uniform/H_P - 1)*100):.1f}%)\\n\")\n",
    "\n",
    "print(f\"3. Wrong-biased codebook (designed for Q_biased):\")\n",
    "print(f\"   H(P, Q_biased) = {avg_biased:.4f} bits\")\n",
    "print(f\"   Wasted = {avg_biased - H_P:.4f} bits (+{((avg_biased/H_P - 1)*100):.1f}%)\\n\")\n",
    "\n",
    "print(f\"4. Reversed-priority codebook (designed for Q_reversed):\")\n",
    "print(f\"   H(P, Q_reversed) = {avg_reversed:.4f} bits\")\n",
    "print(f\"   Wasted = {avg_reversed - H_P:.4f} bits (+{((avg_reversed/H_P - 1)*100):.1f}%)\\n\")\n",
    "\n",
    "print(\"=\"*70)\n",
    "print(\"CAN WE DO WORSE? YES - CROSS-ENTROPY IS UNBOUNDED!\")\n",
    "print(\"=\"*70)\n",
    "\n",
    "# Show how cross-entropy grows as Q becomes more mismatched\n",
    "Q_bad1 = {'A': 0.01, 'B': 0.33, 'C': 0.33, 'D': 0.33}\n",
    "Q_bad2 = {'A': 0.001, 'B': 0.333, 'C': 0.333, 'D': 0.333}\n",
    "\n",
    "H_PQ_bad1 = cross_entropy(P, Q_bad1)\n",
    "H_PQ_bad2 = cross_entropy(P, Q_bad2)\n",
    "\n",
    "print(f\"\\n5. Very bad Q₁ (A=1%, others≈33% each):\")\n",
    "print(f\"   H(P, Q₁) = {H_PQ_bad1:.4f} bits\")\n",
    "print(f\"   Wasted = {H_PQ_bad1 - H_P:.4f} bits (+{((H_PQ_bad1/H_P - 1)*100):.0f}%)\\n\")\n",
    "\n",
    "print(f\"6. Extremely bad Q₂ (A=0.1%, others≈33% each):\")\n",
    "print(f\"   H(P, Q₂) = {H_PQ_bad2:.4f} bits\")\n",
    "print(f\"   Wasted = {H_PQ_bad2 - H_P:.4f} bits (+{((H_PQ_bad2/H_P - 1)*100):.0f}%)\\n\")\n",
    "\n",
    "print(\"As Q(A) → 0 while P(A) = 60%, we get H(P,Q) → ∞\")\n",
    "\n",
    "print(\"\\n\" + \"=\"*70)\n",
    "print(\"KEY INSIGHTS:\")\n",
    "print(\"=\"*70)\n",
    "print(\"Cross-entropy H(P,Q) = -∑ P(x) log₂ Q(x)\")\n",
    "print(\"\\nAlways: H(P,Q) ≥ H(P)\")\n",
    "print(\"Equality only when Q = P (perfect match!)\")\n",
    "print(\"\\nCross-entropy grows when Q assigns:\")\n",
    "print(\"  • Low probabilities to symbols that P considers likely\")\n",
    "print(\"  • High probabilities to symbols that P considers unlikely\")\n",
    "print(f\"\\nWith code lengths (1,2,3,3): ranges from {avg_optimal:.2f} to {avg_reversed:.2f} bits\")\n",
    "print(\"With arbitrarily bad Q: can approach infinity!\")\n",
    "print(\"=\"*70)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ab0480da",
   "metadata": {},
   "source": [
    "## رسم تلخيصي\n",
    "\n",
    "لننشئ مخطّط أعمدة يقارن الحالات الأربع، من الترميز الأمثل إلى الترميز ذي الأولويات المعكوسة."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86ebd76b",
   "metadata": {},
   "source": [
    "### التمرين 15: استكشاف تفاعلي — النطاق الكامل لخيارات الترميز\n",
    "\n",
    "ماذا عن **جميع التخصيصات الأخرى للشفرات** بين الحالة المثلى وحالة الأولويات المعكوسة؟ لنرسم النطاق الكامل للإمكانات عند استخدام أطوال الشفرات (1، 2، 3، 3)!\n",
    "\n",
    "**تمييز أساسي:**\n",
    "\n",
    "يعرض الرسم كميتين مترابطتين:\n",
    "\n",
    "1. **متوسط طول الشفرة** (المحور الأفقي): $\\sum_x P(x) L(x)$.\n",
    "   - قيمة عملية تنتج عن أطوال الشفرات الصحيحة في ترميز هوفمان.\n",
    "   - وهي ما حسبناه في الأشجار أعلاه: 1.6 و2.0 و2.2 و2.7 بت.\n",
    "2. **الإنتروبيا المتقاطعة** $H(P,Q) = -\\sum_x P(x) \\log_2 Q(x)$.\n",
    "   - قيمة نظرية قد تكون كسرية.\n",
    "   - يتطلّب حسابها معرفة التوزيع $Q$ المقصود.\n",
    "   - عند تصميم ترميز هوفمان لتوزيع معيّن، تكون الأطوال تقريبًا $L(x) \\approx -\\log_2 Q(x)$، وقد يختلف المتوسط عن الإنتروبيا المتقاطعة لذلك التوزيع.\n",
    "\n",
    "إذا عرّفنا التوزيع الضمني لشجرة بادئات كاملة تحديدًا على أنه $Q(x)=2^{-L(x)}$، فإن متوسط الطول يساوي الإنتروبيا المتقاطعة لهذا التوزيع الضمني تمامًا. وقد يختلف هذا التوزيع عن التوزيع الذي صُمّم له ترميز هوفمان أصلًا.\n",
    "\n",
    "مرّر المؤشّر فوق النقاط لرؤية القيم والتوزيع $Q$ المعروض!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6c625c2d",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Expected: Interactive plot showing all possible code assignments\")\n",
    "print(\"Hover over points to see average bits and cross-entropy values\\n\")\n",
    "\n",
    "Chapter2.interactive_cross_entropy().show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dd9d0384",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Expected: Bar chart comparing all four coding schemes\")\n",
    "print(\"Shows the range from optimal to reversed-priority\\n\")\n",
    "\n",
    "Chapter2.plot_cross_entropy_spectrum(avg_optimal, avg_uniform, avg_biased, avg_reversed, H_P)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "665171f5",
   "metadata": {},
   "source": [
    "### أبرز الأفكار\n",
    "\n",
    "**ما تعلّمناه:**\n",
    "\n",
    "1. تقيس **الإنتروبيا المتقاطعة** $H(P,Q) = -\\sum_{x} P(x) \\log_2 Q(x)$ المتوسط النظري للبتات اللازمة لترميز بيانات من $P$ بافتراض التوزيع $Q$.\n",
    "\n",
    "2. **دائمًا** $H(P,Q) \\geq H(P)$، وتتحقق المساواة عندما $Q = P$.\n",
    "\n",
    "3. **للإنتروبيا المتقاطعة حد أدنى، لكن ليس لها حد أعلى:**\n",
    "   - **الحد الأدنى:** $H(P) \\approx 1.57$ بت (إنتروبيا شانون، الحد النظري).\n",
    "   - **هوفمان الأمثل:** 1.6 بت (الحد العملي في هذا المثال باستخدام أطوال صحيحة لكل حرف).\n",
    "   - **عند تثبيت الأطوال (1، 2، 3، 3):** يتراوح متوسط الطول من 1.6 بت (الأمثل) إلى 2.7 بت (الأولويات المعكوسة).\n",
    "   - **لا حد أعلى:** إذا أسند $Q$ احتمالات شديدة الانخفاض لرموز يُرجّحها $P$، فقد تتجه $H(P,Q)$ إلى ما لا نهاية.\n",
    "   - السبب رياضي: إذا كان $P(A)=0.6$ و$Q(A)\\to 0$، فإن $-P(A)\\log_2 Q(A)\\to\\infty$.\n",
    "\n",
    "4. **الافتراضات الخاطئة مكلفة:** إسناد احتمالات صغيرة للرموز الشائعة في البيانات يهدر البتات.\n",
    "\n",
    "**الصلة بتعلّم الآلة:**\n",
    "\n",
    "في نماذج اللغة العصبية:\n",
    "\n",
    "- **P** هو التوزيع الحقيقي للرموز التالية في البيانات.\n",
    "- **Q** هو التوزيع الذي يتنبّأ به النموذج.\n",
    "- **خسارة الإنتروبيا المتقاطعة بالبت** هي $H(P,Q) = -\\sum_x P(x)\\log_2 Q(x)$.\n",
    "- **التدريب** يعدّل $Q$ لتقليل $H(P,Q)$ وتقريبها من $H(P)$."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-bits-nats-md",
   "metadata": {},
   "source": [
    "## الجزء الثالث: الانتقال من البت إلى النات\n",
    "\n",
    "_يقابل القسم 5 من الدرس._\n",
    "\n",
    "**استخدمنا حتى الآن $\\log_2$ ووحدة البت. من الآن فصاعدًا، نستخدم $\\ln$ ووحدة النات.**\n",
    "\n",
    "لماذا ننتقل؟\n",
    "\n",
    "- تستخدم مكتبات تعلّم الآلة مثل PyTorch وTensorFlow اللوغاريتم الطبيعي.\n",
    "- المشتقات أبسط: $\\frac{d}{dx}(\\ln x) = \\frac{1}{x}$.\n",
    "- هذا هو الاصطلاح الشائع في أوراق تعلّم الآلة وأطره البرمجية.\n",
    "\n",
    "**التحويل:** $\\text{bits} = \\frac{\\text{nats}}{\\ln(2)} \\approx \\text{nats} \\times 1.443$\n",
    "\n",
    "**التمرين 16:** قارن الإنتروبيا المتقاطعة بوحدتي البت والنات في الخلية التالية."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-bits-nats",
   "metadata": {},
   "outputs": [],
   "source": [
    "### Exercise 16: Compare Bits vs Nats\n",
    "\n",
    "# Compare bits vs nats\n",
    "P = [0.7, 0.2, 0.1]\n",
    "Q = [0.65, 0.25, 0.1]\n",
    "\n",
    "# Cross entropy in nats (natural log)\n",
    "ce_nats = -sum(p * math.log(q) if q > 0 else 0 for p, q in zip(P, Q))\n",
    "\n",
    "# In bits (log2)\n",
    "ce_bits = -sum(p * math.log2(q) if q > 0 else 0 for p, q in zip(P, Q))\n",
    "\n",
    "print(f\"Cross-entropy in NATS: {ce_nats:.4f}\")\n",
    "print(f\"Cross-entropy in BITS: {ce_bits:.4f}\")\n",
    "print(f\"Ratio: {ce_bits / ce_nats:.4f} ≈ 1 / ln(2) ≈ 1.443\")\n",
    "\n",
    "print(\"\\nFrom now on, we use NATS (natural log).\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-summary",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## الملخّص والصلة بتعلّم الآلة\n",
    "\n",
    "### ما تعلّمناه\n",
    "\n",
    "**أساسيات نظرية المعلومات:**\n",
    "\n",
    "1. **الإنتروبيا** $H(P)$ هي الحد النظري الأمثل لحجم الضغط للتوزيع $P$، بوحدة النات أو البت لكل رمز.\n",
    "2. **الإنتروبيا المتقاطعة** $H(P,Q)$ هي التكلفة النظرية عند ضغط بيانات $P$ بترميز مصمّم لـ$Q$.\n",
    "3. نستخدم **النات** (اللوغاريتم الطبيعي) بدل البت في تعلّم الآلة.\n",
    "\n",
    "### الصلة بتعلّم الآلة\n",
    "\n",
    "**لماذا يهمّ ذلك في نماذج اللغة؟**\n",
    "\n",
    "في الفصل الثالث، سترى أن:\n",
    "\n",
    "- **التنبؤ = الضغط:** النموذج الذي يتنبّأ جيدًا يستطيع الضغط جيدًا.\n",
    "- **الإنتروبيا المتقاطعة تصبح خسارة:** تقيس $H(P,Q)$ جودة تنبؤ النموذج $Q$ ببيانات من $P$.\n",
    "- **التدريب يقلّل الهدر:** يقلّل الانحدار المتدرّج الإنتروبيا المتقاطعة بجعل $Q$ أقرب إلى $P$.\n",
    "\n",
    "لغة الضغط (جداول الترميز وأطوال البتات) تقابل لغة التنبؤ (الاحتمالات والخسائر). وقد بيّن شانون الصلة الرياضية بينهما.\n",
    "\n",
    "---\n",
    "\n",
    "## الخطوات التالية\n",
    "\n",
    "يطبّق **[الفصل الثالث](../3/index.qmd)** هذه المفاهيم على بيانات حقيقية:\n",
    "\n",
    "- تسلسلات لعبة حجر وورقة ومقص.\n",
    "- النماذج ثنائية الحروف في النصوص.\n",
    "- مصفوفات الانتقال واستثمار الأنماط.\n",
    "- عائق الذاكرة $V^2$ الذي يدفعنا إلى استخدام الشبكات العصبية."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2b2e8184",
   "metadata": {},
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "llms",
   "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.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
