{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "480f9a0e",
   "metadata": {
    "editable": true,
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-b5ef3c2f2dca81de",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "# 1. Programmierungübung: Python Tutorial Teil 2\n",
    "Willkommen zum zweiten Teil der ersten Programmierübung Einführung in Python 3.\n",
    "\n",
    "Wenn Sie Fragen oder Verbesserungsvorschläge zum Inhalt oder Struktur der Notebooks haben, dann können sie eine E-Mail an Paul Nowitzki ([p.nowitzki@tu-bs.de](mailto:p.nowitzki@tu-bs.de?subject=[SigSys]%20Feedback%20Programmierübung&amp;cc=besser@ifn.ing.tu-bs.de,%20le@ifn.ing.tu-bs.de)), Karl Besser ([besser@ifn.ing.tu-bs.de](mailto:besser@ifn.ing.tu-bs.de)) oder Martin Le ([le@ifn.ing.tu-bs.de](mailto:le@ifn.ing.tu-bs.de)) schreiben.\n",
    "\n",
    "Link zu einem Python Spickzettel: [hier](https://s3.amazonaws.com/assets.datacamp.com/blog_assets/PythonForDataScience.pdf)\n",
    "\n",
    "Link zu den Dokumentationen der hier verwendeten Pakete finden Sie hier: [numpy](https://numpy.org/doc/1.20/), [sympy](https://docs.sympy.org/latest/index.html) und [matplotlib](https://matplotlib.org/)\n",
    "\n",
    "Der Großteil des Python-Tutorials stammt aus der Veranstaltung _Deep Learning Lab_ und von [www.python-kurs.eu](https://www.python-kurs.eu/python3_kurs.php) und wurde für _Signale und Systeme_ angepasst."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a960ea6a",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-3e540637ce0cf04e",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import sympy as sp\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f7db248",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-d1b6225195341484",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "## Aufgabe 2-1: Numpy Arrays"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "089a51bc",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-095d0c8663473ea9",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "*Hinweis:* Viele der Befehle in `numpy` haben eine sehr große Ähnlichkeit zu den Befehlen in Matlab. Wenn Sie bereits Erfahrung mit Matlab haben, können Sie Teile Ihres Wissens schnell und einfach übertragen."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e4ad5db2",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-e703bd590336dbb9",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "**Aufgabe**: Definieren Sie zwei Numpy-Arrays `a` und `b` mit fünf beliebigen Zahlen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bd93bfef",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-38558c816fdeb383",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# BEGIN SOLUTION\n",
    "a = np.random.randn(5)\n",
    "b = np.random.randn(5)\n",
    "# END SOLUTION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8b623711",
   "metadata": {
    "nbgrader": {
     "grade": true,
     "grade_id": "cell-9aec33e8d27e8106",
     "locked": true,
     "points": 1,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "assert len(a) == 5\n",
    "assert len(b) == 5\n",
    "assert isinstance(a, np.ndarray)\n",
    "assert isinstance(b, np.ndarray)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c134b1d8",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-b503ba72573ea20e",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "**Aufgabe**: Addieren Sie die Arrays `a` und `b` element-weise und speichern Sie das Ergebnis in der Variable `c`. Führen Sie die Rechnung analog für die Multiplikation aus und speichern Sie das Ergebnis in `m`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2823ae03",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-1188bd957b9e1c98",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# BEGIN SOLUTION\n",
    "c = a + b\n",
    "m = a * b\n",
    "# END SOLUTION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a6b6b68b",
   "metadata": {
    "nbgrader": {
     "grade": true,
     "grade_id": "cell-b6c5a77a33bde905",
     "locked": true,
     "points": 2,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "assert isinstance(c, np.ndarray)\n",
    "assert isinstance(m, np.ndarray)\n",
    "### BEGIN HIDDEN TESTS\n",
    "assert np.allclose(c, a+b)\n",
    "assert np.allclose(m, a*b)\n",
    "### END HIDDEN TESTS"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3bd6887d",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-7922eb482763dc29",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "## Aufgabe 2-2: Mehrdimensionale Arrays"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a428a87",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-1763af54eb134b32",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "**Aufgabe**: Erzeugen Sie die Matrizen $$A = \\begin{pmatrix}7 & 2\\\\-4 & 42\\end{pmatrix} \\quad \\text{und} \\quad B = \\begin{pmatrix}-5 & 4\\\\3 & -10\\end{pmatrix}$$ als zwei-dimensionales Numpy Array und speichern Sie diese in Variablen `A` und `B`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "70187580",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-8149b06e15a5d0a0",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# BEGIN SOLUTION\n",
    "A = np.array([[7, 2], [-4, 42]])\n",
    "B = np.array([[-5, 4], [3, -10]])\n",
    "# END SOLUTION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "758568f0",
   "metadata": {
    "nbgrader": {
     "grade": true,
     "grade_id": "cell-204f2d8dca887710",
     "locked": true,
     "points": 1,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "assert np.shape(A) == (2, 2)\n",
    "assert np.shape(B) == (2, 2)\n",
    "assert isinstance(A, np.ndarray)\n",
    "assert isinstance(B, np.ndarray)\n",
    "### BEGIN HIDDEN TESTS\n",
    "assert np.allclose(A, np.array([[7, 2], [-4, 42]]))\n",
    "assert np.allclose(B, np.array([[-5, 4], [3, -10]]))\n",
    "### END HIDDEN TESTS"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "77221d98",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-97c2cafe4fbb33c1",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "**Aufgabe:** Addieren Sie die Matrizen `A` und `B` und speichern Sie das Ergebnis in der Variable `C`. Führen Sie die Rechnung analog für die Matrix-Multiplikation ($A\\cdot B$) aus und speichern Sie das Ergebnis in `M`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9ec28862",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-75b4095bcaa5ab4a",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# BEGIN SOLUTION\n",
    "C = A + B\n",
    "M = A @ B\n",
    "# END SOLUTION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1807fc05",
   "metadata": {
    "nbgrader": {
     "grade": true,
     "grade_id": "cell-e897c81ec0545569",
     "locked": true,
     "points": 2,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "assert isinstance(C, np.ndarray)\n",
    "assert isinstance(M, np.ndarray)\n",
    "assert np.shape(C) == (2, 2)\n",
    "assert np.shape(M) == (2, 2)\n",
    "### BEGIN HIDDEN TESTS\n",
    "assert np.allclose(C, A+B)\n",
    "assert np.allclose(M, A@B)\n",
    "### END HIDDEN TESTS"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60ca6b51",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-019cd9f4d679a343",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "## Aufgabe 2-3: Eigenwertzerlegung"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e60997af",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-0e95a972c78dd588",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "**Aufgabe:** Führen Sie die Eigenwertzerlegung der Matrizen `A` und `C` durch. Speichern Sie die Eigenwerte in den Variablen `l_A` und `l_C`, sowie die dazugehörigen Eigenvektoren in den Variablen `v_A` und `v_C`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0e6e2de9",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-da0326102531dd25",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "### BEGIN SOLUTION\n",
    "l_A, v_A = np.linalg.eig(A)\n",
    "l_C, v_C = np.linalg.eig(C)\n",
    "### END SOLUTION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cd158192",
   "metadata": {
    "nbgrader": {
     "grade": true,
     "grade_id": "cell-7119f54a5fe54554",
     "locked": true,
     "points": 1,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "assert len(l_A) == 2\n",
    "assert np.shape(v_C) == (2, 2)\n",
    "### BEGIN HIDDEN TESTS\n",
    "assert np.allclose(np.sort(l_A), [7.230083961, 41.76991604], atol=0.001)\n",
    "assert np.allclose(np.sort(l_C), [2.201351413, 31.79864859], atol=0.001)\n",
    "### END HIDDEN TESTS"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e83039c3",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-3f4a2620cf6d4006",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "## Aufgabe 2-4: Sympy"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0cd374e0",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-3769021537c05f9a",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "Mit Sympy können Sie symbolische Rechnungen in Python ausführen.\n",
    "\n",
    "**Aufgabe:** Definieren Sie die Funktionen\n",
    "\n",
    "$\n",
    "\\begin{align}\n",
    "f(x) &= \\sin(3x)\\\\\n",
    "g(x) &= \\exp\\left(\\frac{x}{2}\\right) - 7x^3\\\\\n",
    "\\end{align}\n",
    "$\n",
    "\n",
    "in den Variablen `f` und `g`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "56b77c4f",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-c1bc77e56c226d65",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "x = sp.Symbol('x')\n",
    "### BEGIN SOLUTION\n",
    "f = sp.sin(3*x)\n",
    "g = sp.exp(x/2) - 7*(x**3)\n",
    "### END SOLUTION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7286cac1",
   "metadata": {
    "nbgrader": {
     "grade": true,
     "grade_id": "cell-529c99fe459dccce",
     "locked": true,
     "points": 2,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# Hier werden die Loesungen getestet...\n",
    "### BEGIN HIDDEN TESTS\n",
    "samples_x = np.random.randn(10)\n",
    "samples_f = sp.lambdify(x, f, \"numpy\")(samples_x)\n",
    "samples_g = sp.lambdify(x, g, \"numpy\")(samples_x)\n",
    "assert np.allclose(samples_f, np.sin(3*samples_x), atol=0.001)\n",
    "assert np.allclose(samples_g, np.exp(samples_x/2)-7*(samples_x**3), atol=0.001)\n",
    "### END HIDDEN TESTS"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c2dd86f",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-ce06db1fa873d0a8",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "**Aufgabe:** Berechnen Sie die Stammfunktionen von `f` mittels [Integration von Sympy](https://docs.sympy.org/latest/modules/integrals/integrals.html) und speichern Sie diese als Sympy-Funktion in `F`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "717d274b",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-63502fc38661e8f8",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "### BEGIN SOLUTION\n",
    "F = sp.integrate(f, x)\n",
    "### END SOLUTION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9d33c865",
   "metadata": {
    "nbgrader": {
     "grade": true,
     "grade_id": "cell-4bc1f83a8304d373",
     "locked": true,
     "points": 1,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "# Hier werden die Loesungen getestet...\n",
    "### BEGIN HIDDEN TESTS\n",
    "samples_x = np.random.randn(10)\n",
    "samples_F = sp.lambdify(x, F, \"numpy\")(samples_x)\n",
    "assert np.allclose(samples_F, -np.cos(3*samples_x)/3., atol=0.001)\n",
    "### END HIDDEN TESTS"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3e1758f8",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-389527b1733be1e8",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "**Aufgabe:** Berechnen Sie das bestimmte Integral $$G=\\int_{20}^{25} g(x)\\mathrm{d}x$$ und speichern Sie die Lösung als float in der Variablen `G`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8565c8e6",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-b867f76246767778",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "### BEGIN SOLUTION\n",
    "G = float(sp.integrate(g, (x, 20, 25)))\n",
    "### END SOLUTION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b7ae55b7",
   "metadata": {
    "nbgrader": {
     "grade": true,
     "grade_id": "cell-a107f3748f1d838e",
     "locked": true,
     "points": 1,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "assert isinstance(G, float)\n",
    "### BEGIN HIDDEN TESTS\n",
    "assert np.isclose(G, -7*25**4/4 + 7*20**4/4 - 2*np.exp(20/2) + 2*np.exp(25/2), atol=0.001)\n",
    "### END HIDDEN TESTS"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b8c28bc1",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-15cba5457c117498",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "## Aufgabe 2-5: Matplotlib"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f0abbc39",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-aaff0f486439460f",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "**Aufgabe:** Plotten Sie die Punkte $(a_i, b_i)$, wobei $a_i$ und $b_i$ die Elemente aus den Arrays `a` und `b` sind.  \n",
    "Es sollen zum einen die Punkte selbst markiert werden und zum anderen sollen sie zusätzlich miteinander verbunden werden.\n",
    "\n",
    "Geben Sie der x-Achse die Beschriftung `\"Werte aus a\"` und der y-Achse die Beschriftung `\"Werte aus b\"`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "732be88f",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-7d1fc64a2ceac3a3",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "### BEGIN SOLUTION\n",
    "plt.plot(a, b, 'o-')\n",
    "plt.xlabel(\"Werte aus a\")\n",
    "plt.ylabel(\"Werte aus b\")\n",
    "### END SOLUTION\n",
    "axis = plt.gca()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a43077fa",
   "metadata": {
    "nbgrader": {
     "grade": true,
     "grade_id": "cell-1a8cb5ce1d117f15",
     "locked": true,
     "points": 2,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "outputs": [],
   "source": [
    "from plotchecker import LinePlotChecker\n",
    "pc = LinePlotChecker(axis)\n",
    "### BEGIN HIDDEN TESTS\n",
    "pc.assert_xlabel_equal(\"Werte aus a\")\n",
    "pc.assert_ylabel_equal(\"Werte aus b\")\n",
    "pc.assert_x_data_equal([a])\n",
    "pc.assert_y_data_equal([b])\n",
    "### END HIDDEN TESTS"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5cf8d195",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-235092fd5dc92284",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "## Aufgabe 2-6: Effizienter Fibonacci-Algorithmus"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "91624223",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-da5d467c21a05339",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    },
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "Die im ersten Teil implementierte Fibonacci-Funktion löst zwar das Problem, besitzt aber eine  Laufzeit von $O(2^n)$ mit Rekursion und $O(n)$ mit einem iterativen Ansatz, weshalb sich diese Ansätze nicht besonders gut zur Berechnung von großen Fibonacci-Zahlen eignen.\n",
    "\n",
    "Um den Fibonacci-Algorithmus in logarithmischer Laufzeit $O(\\log n)$ zu implementieren gibt es den folgenden Ansatz:\n",
    "\n",
    "Die ersten beiden Fibonacci-Zahlen $f_0$ und $f_1$ werden zum Vektor\n",
    "$\\vec{F_1} =\n",
    "\\left(\\begin{align}\n",
    "& f_1\\\\\n",
    "& f_0\n",
    "\\end{align}\\right)\n",
    "$\n",
    "zusammengefasst.\n",
    "\n",
    "Durch Multiplikation der Matrix $\\mathbf{A} = \\begin{pmatrix}1 & 1\\\\1 & 0\\end{pmatrix}$ mit $F_1$ lässt sich der Vektor $\\vec{F_2} = \\mathbf{A} \\cdot \\vec{F_1} =\n",
    "\\left(\\begin{align}\n",
    "& f_2\\\\\n",
    "& f_1\n",
    "\\end{align}\\right)\n",
    "$ berechnen. Diese Multiplikation kann beliebig oft vorgenommen werden, bis der gewünschte Vektor $\\vec{F_n} = \\mathbf{A}^n \\cdot \\vec{F_1}$ erreicht ist.\n",
    "\n",
    "Die logarithmische Laufzeit kann durch geschicktes Berechnen von $\\mathbf{A}^n$ erzielt werden, indem die folgende Rekursions-Vorschrift angewandt wird:\n",
    "\n",
    "$\n",
    "\\mathbf{A}^n =\n",
    "\\begin{cases}\n",
    "\\mathbf{A} & \\mathrm{wenn}\\; n = 1\\\\\n",
    "\\mathbf{A}^{\\frac{n}{2}} \\cdot \\mathbf{A}^{\\frac{n}{2}} & \\mathrm{wenn}\\; n > 1\\;\\mathrm{und}\\; n\\; \\mathrm{gerade}\\\\\n",
    "\\mathbf{A} \\cdot \\mathbf{A}^{\\frac{n-1}{2}} \\cdot \\mathbf{A}^{\\frac{n-1}{2}} & \\mathrm{wenn}\\; n > 1\\;\\mathrm{und}\\; n\\; \\mathrm{ungerade}\n",
    "\\end{cases}\n",
    "$\n",
    "\n",
    "Der Laufzeitvorteil entsteht dabei dadurch, dass jedes der beiden $\\mathbf{A}^{\\frac{n}{2}}$ bzw. $\\mathbf{A}^{\\frac{n-1}{2}}$ in jedem Rekursionsschritt nur einmal berechnet werden muss.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8408ffda",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-ae233ff5332b301e",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    },
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "**Aufgabe:** Deklarieren sie zunächst die Matrix `A` und den Spaltenvektor `F_1` wie oben beschrieben. Da im Folgenden mit großen Zahlen gerechnet werden soll, muss die Deklaration mit dem Parameter `dtype=np.int64` durchgeführt werden, um spätere Overflows zu vermeiden."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c9ec3ac3",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-abb29dd054d75a76",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "### BEGIN SOLUTION\n",
    "A = np.array([[1, 1], [1, 0]], dtype=np.int64)\n",
    "F_1 = np.array([[1], [0]], dtype=np.int64)\n",
    "### END SOLUTION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "29e2481a",
   "metadata": {
    "nbgrader": {
     "grade": true,
     "grade_id": "cell-b9a2651d938be45c",
     "locked": true,
     "points": 2,
     "schema_version": 3,
     "solution": false,
     "task": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# Hier werden die Loesungen getestet...\n",
    "assert A.dtype == np.int64\n",
    "assert F_1.dtype == np.int64\n",
    "### BEGIN HIDDEN TESTS\n",
    "assert np.allclose(A, np.array([[1, 1], [1, 0]]))\n",
    "assert np.allclose(F_1, np.array([[1], [0]]))\n",
    "### END HIDDEN TESTS"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f07282f7",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-5d2f31ad0277ca35",
     "locked": true,
     "schema_version": 3,
     "solution": false,
     "task": false
    }
   },
   "source": [
    "**Aufgabe:** Implementieren Sie nun erneut die Methode `fibonacci(n)`, die die $n$-te Fibonacci-Zahl mit der oben beschriebenen Methode auch für große $n$ berechnet.\n",
    "\n",
    "Hinweis: Für die \"Exponentiation\" einer quadratischen Matrix bietet Numpy die Methode [np.linalg.matrix_power()](https://numpy.org/doc/stable/reference/generated/numpy.linalg.matrix_power.html)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "84d172ed",
   "metadata": {
    "nbgrader": {
     "grade": false,
     "grade_id": "cell-00ec909bed8ed5ec",
     "locked": false,
     "schema_version": 3,
     "solution": true,
     "task": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "def fibonacci(n):\n",
    "### BEGIN SOLUTION\n",
    "    if n < 2:\n",
    "        return n\n",
    "    else:\n",
    "        return (np.linalg.matrix_power(A, n-1) @ F_1)[0]\n",
    "### END SOLUTION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a262cb49",
   "metadata": {
    "nbgrader": {
     "grade": true,
     "grade_id": "cell-118913143e3418fd",
     "locked": true,
     "points": 2,
     "schema_version": 3,
     "solution": false,
     "task": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# Hier werden die Loesungen getestet...\n",
    "assert isinstance(fibonacci(0), int)\n",
    "assert fibonacci(0) == 0\n",
    "assert fibonacci(1) == 1\n",
    "assert fibonacci(2) == 1\n",
    "assert fibonacci(3) == 2\n",
    "assert fibonacci(10) == 55\n",
    "\n",
    "### BEGIN HIDDEN TESTS\n",
    "assert list(map(fibonacci, range(26))) == [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025]\n",
    "\n",
    "#import signal\n",
    "#if hasattr(signal, \"SIGALRM\"):\n",
    "#\n",
    "#    def timeout_handler(signal, frame):\n",
    "#        raise Exception(\"Timeout\")\n",
    "#\n",
    "#    signal.signal(signal.Signals.SIGALRM, timeout_handler)\n",
    "#    signal.alarm(20)\n",
    "\n",
    "assert fibonacci(40) == 102334155\n",
    "assert fibonacci(50) == 12586269025\n",
    "assert fibonacci(90) == 2880067194370816120\n",
    "### END HIDDEN TESTS"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Create Assignment",
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.11.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
