From 5325eaa0b38de6a3e229268da096155f915762ad Mon Sep 17 00:00:00 2001 From: xiaoxiae Date: Mon, 4 Jul 2022 17:08:55 +0200 Subject: [PATCH] Initial commit --- .gitignore | 3 + ant.svg | 52 +++ serial1-reseni.ipynb | 429 +++++++++++++++++++++++++ serial1.ipynb | 472 +++++++++++++++++++++++++++ serial2-reseni.ipynb | 329 +++++++++++++++++++ serial2.ipynb | 748 +++++++++++++++++++++++++++++++++++++++++++ serial3-reseni.ipynb | 452 ++++++++++++++++++++++++++ serial3.ipynb | 631 ++++++++++++++++++++++++++++++++++++ serial4-reseni.ipynb | 446 ++++++++++++++++++++++++++ serial4.ipynb | 675 ++++++++++++++++++++++++++++++++++++++ serial5.ipynb | 508 +++++++++++++++++++++++++++++ 11 files changed, 4745 insertions(+) create mode 100644 .gitignore create mode 100644 ant.svg create mode 100644 serial1-reseni.ipynb create mode 100644 serial1.ipynb create mode 100644 serial2-reseni.ipynb create mode 100644 serial2.ipynb create mode 100644 serial3-reseni.ipynb create mode 100644 serial3.ipynb create mode 100644 serial4-reseni.ipynb create mode 100644 serial4.ipynb create mode 100644 serial5.ipynb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89c5a42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +media/ +.ipynb_checkpoints/ +ignored/ diff --git a/ant.svg b/ant.svg new file mode 100644 index 0000000..f32ebb9 --- /dev/null +++ b/ant.svg @@ -0,0 +1,52 @@ + + + + + + + + + + diff --git a/serial1-reseni.ipynb b/serial1-reseni.ipynb new file mode 100644 index 0000000..a3be5e9 --- /dev/null +++ b/serial1-reseni.ipynb @@ -0,0 +1,429 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "36cee816", + "metadata": {}, + "source": [ + "# Řešení 1. série" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "efe16561", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Manim Community v0.12.0\n",
+       "\n",
+       "
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from manim import *" + ] + }, + { + "cell_type": "markdown", + "id": "24037488", + "metadata": {}, + "source": [ + "## Míchání" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "252d64a3", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%manim -v WARNING -qh Shuffle\n", + "\n", + "from random import *\n", + "\n", + "\n", + "class Shuffle(Scene):\n", + " def construct(self):\n", + " seed(0xdeadbeef)\n", + " \n", + " # počet hodnot k míchání\n", + " n = 5\n", + "\n", + " circles = [\n", + " Circle(color=WHITE, fill_opacity=0.8, fill_color=WHITE).scale(0.6)\n", + " for _ in range(n)\n", + " ]\n", + "\n", + " # mezery mezi kruhy\n", + " spacing = 2\n", + " for i, circle in enumerate(circles):\n", + " circle.shift(RIGHT * (i - (len(circles) - 1) / 2) * spacing)\n", + "\n", + " self.play(*[Write(circle) for circle in circles])\n", + "\n", + " # vybraný kruh\n", + " selected = randint(0, n - 1)\n", + " self.play(circles[selected].animate.set_color(RED))\n", + " self.play(circles[selected].animate.set_color(WHITE))\n", + "\n", + " # postupné zrychlování při navazujících prohozeních\n", + " swaps = 13\n", + " speed_start = 1\n", + " speed_end = 0.2\n", + "\n", + " for i in range(swaps):\n", + " speed = speed_start - abs(speed_start - speed_end) / swaps * i\n", + " \n", + " # vybrání dvou různých náhodných kruhů\n", + " a, b = sample(range(n), 2)\n", + " \n", + " # prohození s o trochu větším úhlem, jinak přes sebe kruhy procházejí, což není hezké\n", + " self.play(\n", + " Swap(circles[a], circles[b]), run_time=speed, path_arc=135 * DEGREES\n", + " )\n", + "\n", + " # zvýraznění původních kruhů\n", + " self.play(circles[selected].animate.set_color(RED))\n", + " self.play(circles[selected].animate.set_color(WHITE))" + ] + }, + { + "cell_type": "markdown", + "id": "c5b2a96d", + "metadata": {}, + "source": [ + "## Třízení" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "72c49f8f", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%manim -v WARNING -qh Sort\n", + "\n", + "from random import *\n", + "\n", + "\n", + "class Sort(Scene):\n", + " def construct(self):\n", + " seed(0xdeadbeef)\n", + " \n", + " # počet hodnot ke třízení\n", + " n = 20\n", + "\n", + " # nejmenší a největší hodnta\n", + " value_min, value_max = 1, 20\n", + "\n", + " # hodnoty v poli\n", + " values = [randint(value_min, value_max) for _ in range(n)]\n", + "\n", + " # šířka čtverce a výška jedné hodnoty (tzn. číslo 5 má výšku 5 * unit_height)\n", + " rectangle_width = 0.2\n", + " unit_height = 0.2\n", + "\n", + " # mezera mezi obdélníky\n", + " rectangle_spacing = 2.5\n", + " \n", + " rectangles = [\n", + " Rectangle(\n", + " width=rectangle_width,\n", + " height=unit_height * v,\n", + " fill_color=WHITE,\n", + " fill_opacity=1,\n", + " )\n", + " for v in values\n", + " ]\n", + " \n", + " # bod, podle kterého budeme rovnat spodek přes funkci align_to\n", + " # aby byly čtverce vycentrované, bude odpovídat spodku nejvyšší hodnoty\n", + " alignment_point = None\n", + " max_value = 0\n", + " for i, v in enumerate(values):\n", + " if max_value < v:\n", + " max_value = v\n", + " alignment_point = Point().shift(DOWN * rectangles[i].height / 2)\n", + "\n", + " # posun obdélníků tak, aby byly rovnoměrně rozprostřené a zarovnané dolů\n", + " for i, rect in enumerate(rectangles):\n", + " rect.shift(\n", + " RIGHT\n", + " * (i - (len(rectangles) - 1) / 2)\n", + " * rectangle_width\n", + " * rectangle_spacing\n", + " ).align_to(alignment_point, DOWN)\n", + "\n", + " self.play(*[Write(r) for r in rectangles])\n", + "\n", + " def animate_at(a, b, duration):\n", + " \"\"\"Animace toho, že se aktuálně díváme na pozice a a b.\"\"\"\n", + " self.play(\n", + " *[\n", + " r.animate.set_color(WHITE if i not in (a, b) else YELLOW)\n", + " for i, r in enumerate(rectangles)\n", + " ],\n", + " run_time=duration,\n", + " )\n", + "\n", + " def animate_swap(a, b, duration):\n", + " \"\"\"Animace prohození a a b (s tím, že v poli values už jsou prohozené).\"\"\"\n", + " self.play(\n", + " # jelikož stretch_to_fit_height stretchuje z prostředku, musíme alignnout na bod\n", + " rectangles[a]\n", + " .animate.stretch_to_fit_height(values[a] * unit_height)\n", + " .align_to(alignment_point, DOWN),\n", + " rectangles[b]\n", + " .animate.stretch_to_fit_height(values[b] * unit_height)\n", + " .align_to(alignment_point, DOWN),\n", + " run_time=duration,\n", + " )\n", + "\n", + " # při prvním průchodu jsou animace pomalejší\n", + " speed_slow = 0.6\n", + " speed_fast = 0.07\n", + "\n", + " for i in range(n):\n", + " speed = speed_slow if i == 0 else speed_fast\n", + " swapped = False\n", + " for j in range(n - i - 1):\n", + " animate_at(j, j + 1, speed)\n", + "\n", + " if values[j] > values[j + 1]:\n", + " values[j], values[j + 1] = values[j + 1], values[j]\n", + "\n", + " animate_swap(j, j + 1, speed)\n", + " swapped = True\n", + "\n", + " # pokud jsme při průchodu nic neprohodili, tak už není co třídit\n", + " if not swapped:\n", + " break\n", + "\n", + " self.play(*[FadeOut(r) for r in rectangles])" + ] + }, + { + "cell_type": "markdown", + "id": "7a4c30a9", + "metadata": {}, + "source": [ + "## Vyhledávání" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "07983bb0", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%manim -v WARNING -qh Search\n", + "\n", + "from random import *\n", + "\n", + "\n", + "class Search(Scene):\n", + " def construct(self):\n", + " seed(0xdeadbeef4) # hezčí vstup :)\n", + " \n", + " # počet hodnot ke třízení\n", + " n = 10\n", + "\n", + " # nejmenší a největší hodnta\n", + " value_min, value_max = 1, n\n", + "\n", + " # hodnoty v poli\n", + " values = sorted([randint(value_min, value_max) for _ in range(n)])\n", + "\n", + " square_side_length = 0.75\n", + " square_spacing = 1.3\n", + "\n", + " squares = [Square(side_length=square_side_length) for v in values]\n", + " numbers = [Tex(f\"${v}$\") for v in values]\n", + "\n", + " # posun čtverců tak, aby byly uprostřed obrazovky\n", + " for i, rect in enumerate(squares):\n", + " rect.shift(\n", + " RIGHT\n", + " * (i - (len(squares) - 1) / 2)\n", + " * square_side_length\n", + " * square_spacing\n", + " )\n", + "\n", + " # posun textu tak, aby byly uvnitř čtverců\n", + " for i, number in enumerate(numbers):\n", + " number.move_to(squares[i])\n", + "\n", + " # pointery na to, kde aktuálně jsme\n", + " pointer_length = 0.4\n", + " l_arrow = Arrow(start=DOWN * pointer_length, end=UP).next_to(squares[0], DOWN)\n", + " r_arrow = Arrow(start=DOWN * pointer_length, end=UP).next_to(squares[-1], DOWN)\n", + "\n", + " self.play(*[Write(s) for s in squares], *[Write(n) for n in numbers])\n", + "\n", + " # vypsání čísla, které hledáme\n", + " target = randint(value_min, value_max)\n", + " text = Tex(f\"Hledáme: ${target}$\").shift(UP * 1.5)\n", + "\n", + " self.play(Write(text))\n", + "\n", + " self.play(Write(l_arrow), Write(r_arrow))\n", + "\n", + " lo, hi = 0, len(values) - 1\n", + "\n", + " def color_in_range(objects, color, range):\n", + " \"\"\"Vrátí seznam animací vybarvení daných objektů v daném rozmezí.\"\"\"\n", + " return [\n", + " o.animate.set_color(color) for i, o in enumerate(objects) if i in range\n", + " ]\n", + "\n", + " # samotný algoritmus\n", + " while lo < hi:\n", + " avg = (lo + hi) // 2\n", + " \n", + " # vykreslení pointeru u aktuálního prvku\n", + " current_arrow = Arrow(start=DOWN * pointer_length, end=UP) \\\n", + " .next_to(squares[avg], DOWN) \\\n", + " .set_color(ORANGE)\n", + " \n", + " self.play(Write(current_arrow))\n", + "\n", + " if values[avg] < target:\n", + " # posunutí levého pointeru\n", + " self.play(\n", + " FadeOut(current_arrow),\n", + " l_arrow.animate.next_to(squares[avg + 1], DOWN),\n", + " *color_in_range(squares, DARK_GRAY, range(lo, avg + 1)),\n", + " *color_in_range(numbers, DARK_GRAY, range(lo, avg + 1)),\n", + " )\n", + "\n", + " lo = avg + 1\n", + " elif values[avg] >= target:\n", + " # posunutí pravého pointeru\n", + " self.play(\n", + " FadeOut(current_arrow),\n", + " r_arrow.animate.next_to(squares[avg], DOWN),\n", + " *color_in_range(squares, DARK_GRAY, range(avg + 1, hi + 1)),\n", + " *color_in_range(numbers, DARK_GRAY, range(avg + 1, hi + 1)),\n", + " )\n", + "\n", + " hi = avg\n", + "\n", + " # nalezení hledané hodnoty\n", + " if values[hi] == target:\n", + " self.play(\n", + " *color_in_range(squares, DARK_GRAY, range(hi)),\n", + " *color_in_range(squares, DARK_GRAY, range(hi+1, n)),\n", + " *color_in_range(numbers, DARK_GRAY, range(hi)),\n", + " *color_in_range(numbers, DARK_GRAY, range(hi+1, n)),\n", + " numbers[hi].animate.set_color(GREEN),\n", + " squares[hi].animate.set_color(GREEN),\n", + " FadeOut(l_arrow),\n", + " )\n", + " break\n", + " \n", + " \n", + " self.play(*[FadeOut(r) for r in numbers + squares + [r_arrow, text]])" + ] + } + ], + "metadata": { + "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.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/serial1.ipynb b/serial1.ipynb new file mode 100644 index 0000000..39091a7 --- /dev/null +++ b/serial1.ipynb @@ -0,0 +1,472 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "substantial-impact", + "metadata": {}, + "source": [ + "# Vítej!" + ] + }, + { + "cell_type": "markdown", + "id": "first-armenia", + "metadata": {}, + "source": [ + "Tento dokument obsahuje zdrojové kódy animací k této sérii seriálu, které jdou upravovat a přehrávat bez toho, aby bylo potřeba cokoliv instalovat. Části dokumentu, u kterých je nalevo `In [ ]:` obsahují kód a jdou spustit - stačí je levým tlačítkem vybrat a nahoře zmáčkout `▶`." + ] + }, + { + "cell_type": "markdown", + "id": "badc1af7", + "metadata": {}, + "source": [ + "Zkuste to u následujícího příkazu, který pro zbytek dokumentu importuje všechno potřebné pro spouštění Manimu (bez něho animace nepoběží)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e03b150c", + "metadata": {}, + "outputs": [], + "source": [ + "from manim import *" + ] + }, + { + "cell_type": "markdown", + "id": "honest-cruise", + "metadata": {}, + "source": [ + "## KSP logo" + ] + }, + { + "cell_type": "markdown", + "id": "governing-increase", + "metadata": {}, + "source": [ + "Animace loga KSP z úvodu seriálu (je v pořádku, že některé jeho části vám ještě nedávají smysl)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "wound-foundation", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qm KSPLogo\n", + "\n", + "\n", + "class KSPLogo(Scene):\n", + " def construct(self):\n", + " # na černém pozadí hrošík není vidět...\n", + " self.camera.background_color = WHITE\n", + "\n", + " hroch = SVGMobject(\"serial1/hroch.svg\").scale(2)\n", + " self.play(Write(hroch))\n", + "\n", + " logo = Tex(\"KSP\", color=BLACK).scale(2.5)\n", + " self.bring_to_back(logo)\n", + "\n", + " self.play(logo.animate.shift(DOWN * 2.3), hroch.animate.shift(UP * 0.6))\n", + "\n", + " logo_expanded = Tex(\"Korespondenční Seminář z Programování\", color=BLACK) \\\n", + " .scale(1.3) \\\n", + " .move_to(logo)\n", + " \n", + " self.play(TransformMatchingShapes(logo, logo_expanded))\n", + " \n", + " self.play(FadeOut(logo_expanded), FadeOut(hroch))" + ] + }, + { + "cell_type": "markdown", + "id": "e6050303", + "metadata": {}, + "source": [ + "Příkaz `manim -v WARNING -qm KSPLogo` volá program Manim na scénu `KSPLogo`. Část `-v WARNING` tlumí výpis nedůležitých informací (zkuste pro zajímavost spustit kód bez něho) a `-qm` nastavuje kvalitu (`q`uality) videa na střední (`m`edium; další dostupné kvality jsou `l`ow, `h`igh a 4`k`)." + ] + }, + { + "cell_type": "markdown", + "id": "1fa02f58", + "metadata": {}, + "source": [ + "## Úvod" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d63a1c09", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qm Intro\n", + "\n", + "\n", + "class Intro(Scene):\n", + " def construct(self):\n", + " # vytvoření objektů čtverce a kruhu (a jejich posunutí)\n", + " square = Square(color=RED).shift(LEFT * 2)\n", + " circle = Circle(color=BLUE).shift(RIGHT * 2)\n", + "\n", + " # napsaní čtverce a kruhu na scénu\n", + " self.play(Write(square), Write(circle))\n", + "\n", + " # schování čtverce a kruhu ze scény\n", + " self.play(FadeOut(square), FadeOut(circle), run_time=2)" + ] + }, + { + "cell_type": "markdown", + "id": "ed91705e", + "metadata": {}, + "source": [ + "## `animate` syntax" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "870dad88", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qm Animate\n", + "\n", + "\n", + "class Animate(Scene):\n", + " def construct(self):\n", + " square = Square(color=RED).shift(LEFT * 2)\n", + " circle = Circle(color=BLUE).shift(RIGHT * 2)\n", + "\n", + " self.play(Write(square), Write(circle))\n", + "\n", + " # posunutí objektů\n", + " self.play(square.animate.shift(UP * 0.5), circle.animate.shift(DOWN * 0.5))\n", + "\n", + " # otočení a vybarvení vnitřku čtverce (průhlednost 80%)\n", + " # zvětšení a vybarvení vnitřku kruhu (průhlednost 80%)\n", + " self.play(\n", + " square.animate.rotate(PI / 2).set_fill(RED, 0.8),\n", + " circle.animate.scale(2).set_fill(BLUE, 0.8),\n", + " )\n", + " \n", + " # úplné přebarvení kruhu i čtverce (obrys i výplň)\n", + " # stejné jako .set_fill() + .set_stroke()\n", + " self.play(\n", + " square.animate.set_color(GREEN),\n", + " circle.animate.set_color(ORANGE),\n", + " )\n", + " \n", + " self.play(FadeOut(square), FadeOut(circle))" + ] + }, + { + "cell_type": "markdown", + "id": "f5464852", + "metadata": {}, + "source": [ + "## Posouvání objektů" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1973f27", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qm NextTo\n", + "\n", + "\n", + "class NextTo(Scene):\n", + " def construct(self):\n", + " # vytvoření čtyřech menších kruhů\n", + " c1, c2, c3, c4 = [Circle(radius=0.5, color=WHITE) for _ in range(4)]\n", + "\n", + " # a jednoho obélníku\n", + " rectangle = Rectangle(width=5, height=3)\n", + " \n", + " # posunutí kruhů tak, aby byly okolo obdélníku\n", + " c1.next_to(rectangle, LEFT) # nalevo od\n", + " c2.next_to(rectangle, UP) # nahoře od\n", + " c3.next_to(rectangle, RIGHT) # napravo od\n", + " c4.next_to(rectangle, DOWN) # dolů od\n", + "\n", + " self.play(*[Write(o) for o in [c1, c2, c3, c4, rectangle]])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f32d0db", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qm MoveTo\n", + "\n", + "\n", + "class MoveTo(Scene):\n", + " def construct(self):\n", + " s1, s2, s3 = [Square() for _ in range(3)]\n", + " \n", + " self.play(*[Write(o) for o in [s1, s2, s3]])\n", + " \n", + " # posunutí čtverců vedle sebe\n", + " self.play(\n", + " s1.animate.next_to(s2, LEFT),\n", + " s3.animate.next_to(s2, RIGHT),\n", + " )\n", + " \n", + " # jim odpovídající čísla\n", + " t1, t2, t3 = [Tex(f\"${i}$\").scale(3) for i in range(3)]\n", + " \n", + " # posunutí čísel do středů čtverců\n", + " t1.move_to(s1)\n", + " t2.move_to(s2)\n", + " t3.move_to(s3)\n", + " \n", + " self.play(*[Write(o) for o in [t1, t2, t3]])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54dc3698", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qm AlignTo\n", + "\n", + "\n", + "class AlignTo(Scene):\n", + " def construct(self):\n", + " # vytvoření tří různě velikých kruhů\n", + " c1, c2, c3 = [Circle(radius=1.5 - i / 3, color=WHITE) for i in range(3)]\n", + "\n", + " self.play(*[Write(o) for o in [c1, c2, c3]])\n", + "\n", + " # c1 a c3 vedle c2\n", + " self.play(\n", + " c1.animate.next_to(c2, LEFT),\n", + " c3.animate.next_to(c2, RIGHT),\n", + " )\n", + "\n", + " # spodek c1 a c3 je stejný jako c2\n", + " self.play(\n", + " c1.animate.align_to(c2, DOWN),\n", + " c3.animate.align_to(c2, DOWN),\n", + " )\n", + "\n", + " # vršek c1, c2 a c3 je vyrovnaný s bodem\n", + " point = Point([0, 2.5, 0])\n", + "\n", + " self.play(\n", + " c1.animate.align_to(point, UP),\n", + " c2.animate.align_to(point, UP),\n", + " c3.animate.align_to(point, UP),\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "53e6209b", + "metadata": {}, + "source": [ + "## Sázení textu a matematiky" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a0de016f", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%%manim -v WARNING -qm TextAndMath\n", + "\n", + "\n", + "class TextAndMath(Scene):\n", + " def construct(self):\n", + " text = Tex(\"Tohle je text!\").shift(LEFT * 2.5)\n", + " \n", + " # ke snazšímu psaní \\ používáme r-stringy (před stringem je 'r')\n", + " # znamená to, že znaky '\\' jsou brány doslova (v normálním stringu by bylo potřeba psát \\\\)\n", + " formula = MathTex(r\"\\sum_{i = 0}^\\infty \\frac{1}{2^i} = 2\").shift(RIGHT * 2.5)\n", + "\n", + " self.play(Write(formula), Write(text))\n", + "\n", + " self.play(FadeOut(formula), FadeOut(text))" + ] + }, + { + "cell_type": "markdown", + "id": "98daca2b", + "metadata": {}, + "source": [ + "## Kostry úloh" + ] + }, + { + "cell_type": "markdown", + "id": "ec0a8c9a", + "metadata": {}, + "source": [ + "### Míchání [4b]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d4d6c6e8", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qm Shuffle\n", + "\n", + "\n", + "class Shuffle(Scene):\n", + " def construct(self):\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8327d92", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh ShuffleExample\n", + "\n", + "class ShuffleExample(Scene):\n", + " def construct(self):\n", + " c11 = Circle(color=WHITE).shift(UP * 1.5 + LEFT * 2)\n", + " c12 = Circle(color=WHITE).shift(UP * 1.5 + RIGHT * 2)\n", + " c21 = Circle(color=WHITE).shift(DOWN * 1.5 + LEFT * 2)\n", + " c22 = Circle(color=WHITE).shift(DOWN * 1.5 + RIGHT * 2)\n", + "\n", + " self.play(Write(c11), Write(c12), Write(c21), Write(c22))\n", + "\n", + " self.play(Swap(c11, c21))\n", + "\n", + " self.play(Swap(c12, c22, path_arc=160 * DEGREES))" + ] + }, + { + "cell_type": "markdown", + "id": "6e2024fc", + "metadata": {}, + "source": [ + "### Třízení [6b]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2736f1be", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qm Sort\n", + "\n", + "\n", + "class Sort(Scene):\n", + " def construct(self):\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54e8ed05", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qm StretchToFitHeightExample\n", + "\n", + "\n", + "class StretchToFitHeightExample(Scene):\n", + " def construct(self):\n", + " s1 = Square().shift(LEFT * 2.5)\n", + " s2 = Square().shift(RIGHT * 2.5)\n", + "\n", + " self.play(Write(s1), Write(s2))\n", + "\n", + " self.play(\n", + " s1.animate.stretch_to_fit_height(3.5),\n", + " s2.animate.set_height(3.5),\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "5e5ac98b", + "metadata": {}, + "source": [ + "### Vyhledávání [5b]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cdcf26cc", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qm Search\n", + "\n", + "\n", + "class Search(Scene):\n", + " def construct(self):\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f62d7a5", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qm ArrowExample\n", + "\n", + "\n", + "class ArrowExample(Scene):\n", + " def construct(self):\n", + " a1 = Arrow(start=UP, end=DOWN).shift(LEFT * 2)\n", + " a2 = Arrow(start=DOWN, end=UP).shift(RIGHT * 2)\n", + "\n", + " self.play(Write(a1), Write(a2))" + ] + } + ], + "metadata": { + "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.9.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/serial2-reseni.ipynb b/serial2-reseni.ipynb new file mode 100644 index 0000000..fbae30f --- /dev/null +++ b/serial2-reseni.ipynb @@ -0,0 +1,329 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "36cee816", + "metadata": {}, + "source": [ + "# Řešení 2. série" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efe16561", + "metadata": {}, + "outputs": [], + "source": [ + "from manim import *" + ] + }, + { + "cell_type": "markdown", + "id": "36ec08cc", + "metadata": {}, + "source": [ + "## Trojúhelník" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7a3da39", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh Triangle\n", + "\n", + "from random import *\n", + "\n", + "\n", + "class Triangle(Scene):\n", + " def construct(self):\n", + " seed(0xDEADBEEF)\n", + "\n", + " # vše trochu zvětšíme\n", + " c = 2\n", + "\n", + " p1 = Dot().scale(c).shift(UP * c)\n", + " p2 = Dot().scale(c).shift(DOWN * c + LEFT * c)\n", + " p3 = Dot().scale(c).shift(DOWN * c + RIGHT * c)\n", + "\n", + " points = VGroup(p1, p2, p3)\n", + " \n", + " self.play(Write(points, lag_ratio=0.5), run_time=1.5)\n", + "\n", + " l1 = Line()\n", + " l2 = Line()\n", + " l3 = Line()\n", + "\n", + " lines = VGroup(l1, l2, l3)\n", + "\n", + " def create_line_updater(a, b):\n", + " \"\"\"Vrátí funkci, která se chová jako updater nějaké úsečky.\"\"\"\n", + " return lambda x: x.become(Line(start=a.get_center(), end=b.get_center()))\n", + "\n", + " l1.add_updater(create_line_updater(p1, p2))\n", + " l2.add_updater(create_line_updater(p2, p3))\n", + " l3.add_updater(create_line_updater(p3, p1))\n", + "\n", + " self.play(Write(lines, lag_ratio=0.5), run_time=1.5)\n", + "\n", + " x = Tex(\"x\")\n", + " y = Tex(\"y\")\n", + " z = Tex(\"z\")\n", + "\n", + " x.add_updater(lambda x: x.next_to(p1, UP))\n", + " y.add_updater(lambda x: x.next_to(p2, DOWN + LEFT))\n", + " z.add_updater(lambda x: x.next_to(p3, DOWN + RIGHT))\n", + "\n", + " labels = VGroup(x, y, z).scale(c * 0.8)\n", + "\n", + " self.play(FadeIn(labels, shift=UP * 0.2))\n", + "\n", + " circle = Circle()\n", + " circle.add_updater(\n", + " lambda c: c.become(\n", + " Circle.from_three_points(\n", + " p1.get_center(), p2.get_center(), p3.get_center()\n", + " )\n", + " )\n", + " )\n", + "\n", + " self.play(Write(circle))\n", + "\n", + " self.play(\n", + " p2.animate.shift(LEFT + UP),\n", + " p1.animate.shift(RIGHT),\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "171e8840", + "metadata": {}, + "source": [ + "## Vlna" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "252d64a3", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh Wave\n", + "\n", + "from random import *\n", + "\n", + "\n", + "class Wave(Scene):\n", + " def construct(self):\n", + " seed(0xDEADBEEF)\n", + "\n", + " maze_string = \"\"\"\n", + "#######################################################\n", + "# ################# ## #\n", + "# ################## #### #\n", + "# ################# #### #\n", + "# ############### ##### # ##\n", + "# ######### ##### ####\n", + "# ### ###### ######\n", + "# ### ## ##### ### #####\n", + "# #### ######## #### ##### ## #\n", + "# ##### ########## ### ######## # #\n", + "# ##### ########### ######## #\n", + "# #### ########### ########## #\n", + "# ## ########### ########## #\n", + "# #### ############ ############# #\n", + "# ###### ############ ############# #\n", + "# ######### ## ########### ######### # #\n", + "# ############### ######### ####### #\n", + "# ############### ###### ###### #\n", + "# ############### ##### #### #\n", + "# ############# # ## #\n", + "# # ####### ########### ####\n", + "# ### # #################\n", + "# ## #### #################\n", + "##### ###### ##################\n", + "###### ###### ##################\n", + "# ### ### ####### ### ############### #\n", + "# #### ############ #### ####### #\n", + "# ##### ############ ### #\n", + "# ### ########## #\n", + "#######################################################\n", + "\"\"\"\n", + "\n", + " maze = [] # 2D pole čtverců tak, jak ho vidíme\n", + " maze_bool = [] # 2D pole True/False\n", + " all_squares = VGroup()\n", + "\n", + " # parsujeme vstup, řádek po řádku\n", + " for row in maze_string.strip().splitlines():\n", + " maze.append([])\n", + " maze_bool.append([])\n", + "\n", + " for char in row:\n", + " square = Square(\n", + " side_length=0.23,\n", + " stroke_width=1,\n", + " fill_color=WHITE if char == \"#\" else BLACK,\n", + " fill_opacity=1,\n", + " )\n", + "\n", + " maze[-1].append(square)\n", + " maze_bool[-1].append(char == \" \")\n", + " all_squares.add(square)\n", + "\n", + " # rozměry bludiště\n", + " w = len(maze[0])\n", + " h = len(maze)\n", + "\n", + " # rozmístění do gridu\n", + " all_squares.arrange_in_grid(rows=h, buff=0)\n", + "\n", + " self.play(FadeIn(all_squares), run_time=2)\n", + "\n", + " # startovní pozice\n", + " x, y = 1, 1\n", + "\n", + " colors = [\"#ef476f\", \"#ffd166\", \"#06d6a0\", \"#118ab2\"]\n", + "\n", + " # vytvoříme si slovník se vzdálenostmi od startu\n", + " distances = {(x, y): 0}\n", + " stack = [(x, y, 0)]\n", + "\n", + " while len(stack) != 0:\n", + " x, y, d = stack.pop(0)\n", + "\n", + " for dx, dy in ((0, 1), (1, 0), (-1, 0), (0, -1)):\n", + " nx, ny = dx + x, dy + y\n", + "\n", + " # není potřeba, jelikož vstup je ohraničený a nikde nevyběhneme :)\n", + " #if nx < 0 or nx >= w or ny < 0 or ny >= h:\n", + " # continue\n", + "\n", + " if maze_bool[ny][nx] and (nx, ny) not in distances:\n", + " stack.append((nx, ny, d + 1))\n", + " distances[(nx, ny)] = d + 1\n", + "\n", + " max_distance = max([d for d in distances.values()])\n", + "\n", + " all_colors = color_gradient(colors, max_distance + 1)\n", + "\n", + " # vytvoříme skupiny podle vzdálenosti od startu\n", + " groups = []\n", + " for d in range(max_distance + 1):\n", + " groups.append(\n", + " AnimationGroup(\n", + " *[\n", + " maze[y][x].animate.set_fill(all_colors[d])\n", + " for x, y in distances\n", + " if distances[x, y] == d\n", + " ]\n", + " )\n", + " )\n", + "\n", + " self.play(AnimationGroup(*groups, lag_ratio=0.08))\n" + ] + }, + { + "cell_type": "markdown", + "id": "e6f13b54", + "metadata": {}, + "source": [ + "## Hilbert" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "06ed2d5c", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh Hilbert\n", + "\n", + "\n", + "class Path(VMobject):\n", + " def __init__(self, points, *args, **kwargs):\n", + " super().__init__(self, *args, **kwargs)\n", + " self.set_points_as_corners(points)\n", + "\n", + " def get_important_points(self):\n", + " \"\"\"Vrátí důležité body křivky.\"\"\"\n", + " return list(self.get_start_anchors()) + [self.get_end_anchors()[-1]]\n", + "\n", + "\n", + "class Hilbert(Scene):\n", + " def construct(self):\n", + " directions = [LEFT + DOWN, LEFT + UP, RIGHT + UP, RIGHT + DOWN]\n", + "\n", + " hilbert = Path(directions).scale(3)\n", + "\n", + " self.play(Write(hilbert, lag_ratio=0.5))\n", + "\n", + " for i in range(1, 4):\n", + " # délka jedné úsečky\n", + " new_segment_length = 1 / (2 ** (i + 1) - 1)\n", + "\n", + " # škálování částí křivky tak, aby byla vycentrovaná\n", + " new_scale = (1 - new_segment_length) / 2\n", + "\n", + " # chceme si uchovat původní křivku, abychom pomocí ní vyrovnávali ostatní\n", + " lu = hilbert.copy()\n", + " lu, hilbert = hilbert, lu\n", + "\n", + " self.play(\n", + " lu.animate.scale(new_scale)\n", + " .set_color(DARK_GRAY)\n", + " .align_to(hilbert, directions[1])\n", + " )\n", + "\n", + " ru = lu.copy()\n", + " self.play(ru.animate.align_to(hilbert, directions[2]))\n", + "\n", + " ld, rd = lu.copy(), ru.copy()\n", + " self.play(\n", + " ld.animate.align_to(hilbert, directions[0]).rotate(-PI / 2),\n", + " rd.animate.align_to(hilbert, directions[3]).rotate(PI / 2),\n", + " )\n", + "\n", + " new_hilbert = Path(\n", + " list(ld.flip(LEFT).get_important_points())\n", + " + list(lu.get_important_points())\n", + " + list(ru.get_important_points())\n", + " + list(rd.flip(LEFT).get_important_points())\n", + " )\n", + "\n", + " self.play(Write(new_hilbert, run_time=2 ** i))\n", + "\n", + " self.remove(lu, ru, ld, rd)\n", + "\n", + " hilbert = new_hilbert" + ] + } + ], + "metadata": { + "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.10.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/serial2.ipynb b/serial2.ipynb new file mode 100644 index 0000000..b3bdc99 --- /dev/null +++ b/serial2.ipynb @@ -0,0 +1,748 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "substantial-impact", + "metadata": {}, + "source": [ + "# Vítej!" + ] + }, + { + "cell_type": "markdown", + "id": "first-armenia", + "metadata": {}, + "source": [ + "Tento dokument obsahuje zdrojové kódy animací ke druhé sérii seriálu KSP. Před spouštěním opět nezapomeň Manim importovat spuštěním následujícího řádku!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e03b150c", + "metadata": {}, + "outputs": [], + "source": [ + "from manim import *" + ] + }, + { + "cell_type": "markdown", + "id": "45897e9c", + "metadata": {}, + "source": [ + "## Práce se skupinami objektů" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "wound-foundation", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh VGroupExample\n", + "\n", + "\n", + "class VGroupExample(Scene):\n", + " def construct(self):\n", + " s1 = Square(color=RED)\n", + " s2 = Square(color=GREEN)\n", + " s3 = Square(color=BLUE)\n", + "\n", + " s1.next_to(s2, LEFT)\n", + " s3.next_to(s2, RIGHT)\n", + "\n", + " self.play(Write(s1), Write(s2), Write(s3))\n", + "\n", + " group = VGroup(s1, s2, s3)\n", + "\n", + " # aplikace škálování na celou skupinu\n", + " self.play(group.animate.scale(1.5).shift(UP))\n", + "\n", + " # na skupině můžeme indexovat\n", + " self.play(group[1].animate.shift(DOWN * 2))\n", + "\n", + " # změna barvy se aplikuje na všechny objekty\n", + " self.play(group.animate.set_color(WHITE))\n", + " self.play(group.animate.set_fill(WHITE, 1))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "659287fa", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh ArrangeExample\n", + "\n", + "from random import seed, uniform\n", + "\n", + "\n", + "class ArrangeExample(Scene):\n", + " def construct(self):\n", + " seed(0xDEADBEEF)\n", + "\n", + " # používáme *, protože VGroup bere samotné objekty (viz minulý příklad)\n", + " circles = VGroup(\n", + " *[\n", + " Circle(radius=0.1)\n", + " .scale(uniform(0.5, 4))\n", + " .shift(UP * uniform(-3, 3) + RIGHT * uniform(-5, 5))\n", + " for _ in range(12)\n", + " ]\n", + " )\n", + "\n", + " self.play(FadeIn(circles))\n", + "\n", + " # uspořádání vedle sebe\n", + " self.play(circles.animate.arrange())\n", + "\n", + " # různé odsazení a směry\n", + " self.play(circles.animate.arrange(direction=DOWN, buff=0.1))\n", + " self.play(circles.animate.arrange(buff=0.4))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ff1ab3fd", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh ArrangeInGridExample\n", + "\n", + "from random import seed, uniform\n", + "\n", + "\n", + "class ArrangeInGridExample(Scene):\n", + " def construct(self):\n", + " seed(0xDEADBEEF)\n", + "\n", + " circles = VGroup(\n", + " *[\n", + " Circle(radius=0.1)\n", + " .scale(uniform(0.5, 2))\n", + " .shift(UP * uniform(-3, 3) + RIGHT * uniform(-5, 5))\n", + " for _ in range(9 ** 2)\n", + " ]\n", + " )\n", + "\n", + " self.play(FadeIn(circles))\n", + "\n", + " # uspořádání do mřížky\n", + " self.play(circles.animate.arrange_in_grid())\n", + "\n", + " # různé odsazení a počet řádků/sloupců\n", + " self.play(circles.animate.arrange_in_grid(rows=5, buff=0))\n", + " self.play(circles.animate.arrange_in_grid(cols=12, buff=0.3))" + ] + }, + { + "cell_type": "markdown", + "id": "fc6ccd7e", + "metadata": {}, + "source": [ + "## Přidávání a odebírání objektů" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68f328dd", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh AddRemoveExample\n", + "\n", + "\n", + "class AddRemoveExample(Scene):\n", + " def construct(self):\n", + " square = Square(fill_color=WHITE, fill_opacity=1)\n", + " small_scale = 0.6\n", + "\n", + " triangle = Triangle(fill_opacity=1).scale(small_scale).move_to(square)\n", + "\n", + " self.play(Write(square))\n", + "\n", + " # přidání trojúhelníku pod čtverec\n", + " self.bring_to_back(triangle)\n", + " self.play(square.animate.shift(LEFT * 2))\n", + "\n", + " circle = Circle(fill_opacity=1).scale(small_scale).move_to(square)\n", + "\n", + " # přidání kruhu pod čtverec\n", + " self.bring_to_back(circle)\n", + " self.play(square.animate.shift(RIGHT * 2))\n", + "\n", + " square2 = (\n", + " Square(stroke_color=GREEN, fill_color=GREEN, fill_opacity=1)\n", + " .scale(small_scale)\n", + " .move_to(square)\n", + " )\n", + " \n", + " self.remove(triangle)\n", + " \n", + " # stejné jako self.add\n", + " # přidání dopředu tu spíš nechceme, ale je dobré vidět co dělá\n", + " self.bring_to_front(square2)\n", + "\n", + " self.play(square.animate.shift(RIGHT * 2))" + ] + }, + { + "cell_type": "markdown", + "id": "07e6819f", + "metadata": {}, + "source": [ + "## Překrývající-se animace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d63a1c09", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh AnimationGroupExample\n", + "\n", + "\n", + "class AnimationGroupExample(Scene):\n", + " def construct(self):\n", + " c1 = Square(color=RED)\n", + " c2 = Square(color=GREEN)\n", + " c3 = Square(color=BLUE)\n", + "\n", + " VGroup(c1, c2, c3).arrange(buff=1)\n", + "\n", + " # každá další animace se spustí v polovině té předchozí (0.5)\n", + " self.play(AnimationGroup(Write(c1), Write(c2), Write(c3), lag_ratio=0.5))\n", + "\n", + " # každá další animace se spustí v desetině té předchozí (0.1)\n", + " self.play(AnimationGroup(FadeOut(c1), FadeOut(c2), FadeOut(c3), lag_ratio=0.1))\n", + "\n", + " # jedna z animací může být rovněž skupina, která může mít sama o sobě zpoždění\n", + " self.play(\n", + " AnimationGroup(\n", + " AnimationGroup(Write(c1), Write(c2), lag_ratio=0.1),\n", + " Write(c3),\n", + " lag_ratio=0.5,\n", + " )\n", + " )\n", + "\n", + " # lag_ratio může být i záporné (animace se budou spouštět obráceně)\n", + " self.play(AnimationGroup(FadeOut(c1), FadeOut(c2), FadeOut(c3), lag_ratio=-0.1))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4412a01b", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh VGroupLagRatioExample\n", + "\n", + "\n", + "class VGroupLagRatioExample(Scene):\n", + " def construct(self):\n", + " squares = VGroup(Square(), Square(), Square()).arrange(buff=0.5).scale(1.5)\n", + " \n", + " # postupné vykreslení čtverců\n", + " self.play(Write(squares))\n", + "\n", + " # FadeOut lag_ratio má nulové, animace se vykonají najednou\n", + " self.play(FadeOut(squares))\n", + "\n", + " squares.set_color(BLUE)\n", + " \n", + " # lag_ratio můžeme manuálně nastavit tak, aby se čtverce vykreslily najednou \n", + " self.play(Write(squares, lag_ratio=0))\n", + "\n", + " self.play(FadeOut(squares, lag_ratio=0.5))" + ] + }, + { + "cell_type": "markdown", + "id": "1fa02f58", + "metadata": {}, + "source": [ + "## Práce s pozorností" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3d83c22", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh AttentionExample\n", + "\n", + "\n", + "class AttentionExample(Scene):\n", + " def construct(self):\n", + " c1 = Square()\n", + "\n", + " labels = [\n", + " Tex(\"Flash\"),\n", + " Tex(\"Indicate\"),\n", + " Tex(\"Wiggle\"),\n", + " Tex(\"FocusOn\"),\n", + " Tex(\"Circumscribe\"),\n", + " ]\n", + "\n", + " # labely posuneme dolů (pod čtverec)\n", + " for label in labels:\n", + " label.shift(DOWN * 1.5).scale(1.5)\n", + "\n", + " def switch_labels(i: int):\n", + " \"\"\"Animace přeměny jednoho labelu na druhého.\"\"\"\n", + " return AnimationGroup(\n", + " FadeOut(labels[i], shift=UP * 0.7),\n", + " FadeIn(labels[i + 1], shift=UP * 0.7),\n", + " )\n", + "\n", + " self.play(Write(c1))\n", + "\n", + " self.play(FadeIn(labels[0], shift=UP * 0.5), c1.animate.shift(UP))\n", + "\n", + " # Flash\n", + " self.play(Flash(c1, flash_radius=1.6, num_lines=20))\n", + "\n", + " # Indicate\n", + " self.play(AnimationGroup(switch_labels(0), Indicate(c1), lag_ratio=0.7))\n", + "\n", + " # Wiggle\n", + " self.play(AnimationGroup(switch_labels(1), Wiggle(c1), lag_ratio=0.7))\n", + "\n", + " # FocusOn\n", + " self.play(AnimationGroup(switch_labels(2), FocusOn(c1), lag_ratio=0.7))\n", + "\n", + " # Circumscribe\n", + " self.play(AnimationGroup(switch_labels(3), Circumscribe(c1), lag_ratio=0.7))" + ] + }, + { + "cell_type": "markdown", + "id": "a9d9d8db", + "metadata": {}, + "source": [ + "## Transformace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e2510a0d", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh BasicTransformExample\n", + "\n", + "\n", + "class BasicTransformExample(Scene):\n", + " def construct(self):\n", + " c = Circle().scale(2)\n", + " s = Square().scale(2)\n", + "\n", + " self.play(Write(c))\n", + "\n", + " self.play(Transform(c, s))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8561d8a8", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh BadTransformExample\n", + "\n", + "\n", + "class BadTransformExample(Scene):\n", + " def construct(self):\n", + " good = [Circle(color=GREEN), Square(color=GREEN), Triangle(color=GREEN)]\n", + " bad = [Circle(color=RED), Square(color=RED), Triangle(color=RED)]\n", + "\n", + " # uspořádání do mřížky - nahoře dobré, dole špatné\n", + " VGroup(*(good + bad)).arrange_in_grid(rows=2, buff=1)\n", + "\n", + " self.play(Write(good[0]), Write(bad[0]))\n", + "\n", + " self.play(\n", + " Transform(good[0], good[1]),\n", + " Transform(bad[0], bad[1]),\n", + " )\n", + "\n", + " self.play(\n", + " Transform(good[0], good[2]),\n", + " Transform(bad[1], bad[2]),\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0eda62fe", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh TransformMatchingShapesExample\n", + "\n", + "\n", + "class TransformMatchingShapesExample(Scene):\n", + " def construct(self):\n", + " ksp_matching = Tex(\"KSP\").scale(5)\n", + " ksp_full_matching = Tex(\"Korespondenční Seminář z Programování\")\n", + "\n", + " ksp_regular = ksp_matching.copy().set_color(BLUE)\n", + " ksp_full_regular = ksp_full_matching.copy().set_color(BLUE)\n", + "\n", + " VGroup(ksp_matching, ksp_regular).arrange(direction=DOWN, buff=1)\n", + " ksp_full_matching.move_to(ksp_matching)\n", + " ksp_full_regular.move_to(ksp_regular)\n", + "\n", + " self.play(Write(ksp_matching), Write(ksp_regular))\n", + "\n", + " self.play(\n", + " TransformMatchingShapes(ksp_matching, ksp_full_matching),\n", + " Transform(ksp_regular, ksp_full_regular),\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "310fdca9", + "metadata": {}, + "source": [ + "## Updatery" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84c90216", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh SimpleUpdaterExample\n", + "\n", + "\n", + "class SimpleUpdaterExample(Scene):\n", + " def construct(self):\n", + " square = Square()\n", + " square_label = Tex(\"A neat square.\").next_to(square, UP, buff=0.5)\n", + "\n", + " self.play(Write(square))\n", + " self.play(FadeIn(square_label, shift=UP * 0.5))\n", + "\n", + " def label_updater(obj):\n", + " \"\"\"Updater, který posune objekt nad čtverec.\n", + "\n", + " První parametr (obj) je vždy objekt, na který je updater přidaný.\"\"\"\n", + " obj.next_to(square, UP, buff=0.5)\n", + "\n", + " # popisek čtverce chceme mít fixně nad čtvercem\n", + " square_label.add_updater(label_updater)\n", + "\n", + " # vždy zůstává nad čtvercem\n", + " self.play(square.animate.shift(LEFT * 3))\n", + " self.play(square.animate.scale(1 / 2))\n", + " self.play(square.animate.rotate(PI / 2).shift(RIGHT * 3 + DOWN * 0.5).scale(3))\n", + "\n", + " # odstranění updateru můžeme udělat přes remove_updater\n", + " square_label.remove_updater(label_updater)\n", + " self.play(square.animate.scale(1 / 3))\n", + " self.play(square.animate.rotate(PI / 2))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0a57559", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh BecomeUpdaterExample\n", + "\n", + "\n", + "class BecomeUpdaterExample(Scene):\n", + " def format_point(self, point):\n", + " \"\"\"Formátování dané souřadnice na [x, y].\"\"\"\n", + " return f\"[{point[0]:.2f}, {point[1]:.2f}]\"\n", + "\n", + " def construct(self):\n", + " circle = Circle(color=WHITE)\n", + "\n", + " def circle_label_updater(obj):\n", + " \"\"\"Updater pro label, který jej posouvá nad bod a nastavuje jeho text.\"\"\"\n", + " obj.become(Tex(f\"p = {self.format_point(circle.get_center())}\"))\n", + " obj.next_to(circle, UP, buff=0.35)\n", + "\n", + " self.play(Write(circle))\n", + "\n", + " circle_label = Tex()\n", + "\n", + " # používáme tu trochu trik k šetření kódu - updater voláme proto,\n", + " # abychom nastavili popisek na základní hodnotu a pozici\n", + " circle_label_updater(circle_label)\n", + "\n", + " self.play(FadeIn(circle_label, shift=UP * 0.3))\n", + "\n", + " circle_label.add_updater(circle_label_updater)\n", + "\n", + " # tato animace se bude pravděpodobně renderovat dlouho, protože\n", + " # updater v každém snímku vytváří Tex objekt, což nějakou dobu trvá\n", + " self.play(circle.animate.shift(RIGHT))\n", + " self.play(circle.animate.shift(LEFT * 3 + UP))\n", + " self.play(circle.animate.shift(DOWN * 2 + RIGHT * 2))\n", + " self.play(circle.animate.shift(UP))" + ] + }, + { + "cell_type": "markdown", + "id": "98daca2b", + "metadata": {}, + "source": [ + "## Kostry úloh" + ] + }, + { + "cell_type": "markdown", + "id": "8746ae11", + "metadata": {}, + "source": [ + "### Trojúhelník [3b]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18fb5e35", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh Triangle\n", + "\n", + "\n", + "class Triangle(Scene):\n", + " def construct(self):\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e9471651", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh LineExample\n", + "\n", + "\n", + "class LineExample(Scene):\n", + " def construct(self):\n", + " p1 = Dot()\n", + " p2 = Dot()\n", + " \n", + " points = VGroup(p1, p2).arrange(buff=2.5)\n", + " \n", + " line = Line(start=p1.get_center(), end=p2.get_center())\n", + " \n", + " self.play(Write(p1), Write(p2))\n", + " \n", + " self.play(Write(line))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7bddd3e5", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh CircleFromPointsExample\n", + "\n", + "\n", + "class CircleFromPointsExample(Scene):\n", + " def construct(self):\n", + " p1 = Dot().shift(LEFT + UP)\n", + " p2 = Dot().shift(DOWN * 1.5)\n", + " p3 = Dot().shift(RIGHT + UP)\n", + " \n", + " dots = VGroup(p1, p2, p3)\n", + " \n", + " # vytvoření kruhu ze tří bodů\n", + " circle = Circle.from_three_points(p1.get_center(), p2.get_center(), p3.get_center(), color=WHITE)\n", + " \n", + " self.play(Write(dots), run_time=1.5)\n", + " self.play(Write(circle))" + ] + }, + { + "cell_type": "markdown", + "id": "4de93509", + "metadata": {}, + "source": [ + "### Vlna [6b]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49bb1f67", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh Wave\n", + "\n", + "\n", + "class Wave(Scene):\n", + " def construct(self):\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f48d3810", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh ColorGradientExample\n", + "\n", + "\n", + "class ColorGradientExample(Scene):\n", + " def construct(self):\n", + " rows = 6\n", + " square_count = rows * 9\n", + "\n", + " colors = [\"#ef476f\", \"#ffd166\", \"#06d6a0\", \"#118ab2\"]\n", + " squares = [\n", + " Square(fill_color=WHITE, fill_opacity=1).scale(0.3)\n", + " for _ in range(square_count)\n", + " ]\n", + "\n", + " group = VGroup(*squares).arrange_in_grid(rows=rows)\n", + "\n", + " self.play(Write(group, lag_ratio=0.04))\n", + "\n", + " all_colors = color_gradient(colors, square_count)\n", + "\n", + " self.play(\n", + " AnimationGroup(\n", + " *[s.animate.set_color(all_colors[i]) for i, s in enumerate(squares)],\n", + " lag_ratio=0.02,\n", + " )\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "159f8abb", + "metadata": {}, + "source": [ + "### Hilbert [6b]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d95d2328", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh Hilbert\n", + "\n", + "\n", + "class Path(VMobject):\n", + " def __init__(self, points, *args, **kwargs):\n", + " super().__init__(self, *args, **kwargs)\n", + " self.set_points_as_corners(points)\n", + "\n", + "\n", + "class Hilbert(Scene):\n", + " def construct(self):\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a1bfc52", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh PathExample\n", + "\n", + "\n", + "class Path(VMobject):\n", + " def __init__(self, points, *args, **kwargs):\n", + " super().__init__(self, *args, **kwargs)\n", + " self.set_points_as_corners(points)\n", + " \n", + " def get_important_points(self):\n", + " \"\"\"Vrátí důležité body křivky.\"\"\"\n", + " # drobné vysvětlení: Manim k vytváření úseček používá kvadratické Bézierovy křivky\n", + " # - každá taková křivka má čtyři body -- dva krajní a dva řidicí\n", + " # - path.points vrací *všechny* body, což po několika iteracích roste exponenciálně\n", + " # \n", + " # proto používáme funkce get_*_anchors, které vrací pouze krajní body\n", + " # pro více detailů viz https://en.wikipedia.org/wiki/Bézier_curve\n", + " return list(self.get_start_anchors()) + [self.get_end_anchors()[-1]]\n", + "\n", + "\n", + "class PathExample(Scene):\n", + " def construct(self):\n", + " path = Path([LEFT + UP, LEFT + DOWN, RIGHT + UP, RIGHT + DOWN])\n", + "\n", + " self.play(Write(path))\n", + "\n", + " # opraveno po vydání seriálu, předtím jsme používali path.points]), vysvětlení viz výše\n", + " path_points = VGroup(*[Dot().move_to(point) for point in path.get_important_points()])\n", + " \n", + " self.play(Write(path_points))\n", + " \n", + " path2 = path.copy()\n", + " path3 = path.copy()\n", + "\n", + " self.play(\n", + " path2.animate.next_to(path, LEFT, buff=1),\n", + " path3.animate.next_to(path, RIGHT, buff=1),\n", + " )\n", + " \n", + " # pozor, tohle není úplně intuitivní\n", + " # LEFT flipne dolů, jelikož určuje osu, přes kterou se objekt přetočí\n", + " self.play(\n", + " path2.animate.flip(),\n", + " path3.animate.flip(LEFT),\n", + " )" + ] + } + ], + "metadata": { + "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.10.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/serial3-reseni.ipynb b/serial3-reseni.ipynb new file mode 100644 index 0000000..570b429 --- /dev/null +++ b/serial3-reseni.ipynb @@ -0,0 +1,452 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "36cee816", + "metadata": {}, + "source": [ + "# Řešení 3. série" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efe16561", + "metadata": {}, + "outputs": [], + "source": [ + "from manim import *" + ] + }, + { + "cell_type": "markdown", + "id": "36ec08cc", + "metadata": {}, + "source": [ + "## Grafový algoritmus" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7a3da39", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh GraphAlgorithm\n", + "\n", + "from random import *\n", + "import networkx as nx\n", + "\n", + "\n", + "class GraphAlgorithm(Scene):\n", + " def construct(self):\n", + " seed(0xDEADBEEF)\n", + "\n", + " n = 14\n", + " p = 3 / n\n", + "\n", + " # generujeme, dokud nemáme spojitý graf\n", + " graph = None\n", + " while graph is None or not nx.is_connected(graph):\n", + " graph = nx.generators.random_graphs.gnp_random_graph(n, p)\n", + "\n", + " g = (\n", + " Graph(graph.nodes, graph.edges, layout_config={\"seed\": 0})\n", + " .scale(2.7)\n", + " .rotate(PI / 12)\n", + " )\n", + "\n", + " explored = set()\n", + "\n", + " def dfs(v, position_object):\n", + " \"\"\"Rekurzivní DFS, které posouvá position_object.\"\"\"\n", + " neighbours = list(graph.neighbors(v))\n", + "\n", + " for w in neighbours:\n", + " if w in explored:\n", + " continue\n", + "\n", + " # Manim bohužel neorientovaný graf bere jako orientovaný\n", + " edge = (v, w) if (v, w) in g.edges else (w, v)\n", + "\n", + " unexplored_neighbours = [w for w in neighbours if w not in explored]\n", + " unexplored_neighbour_edges = [\n", + " (a, b)\n", + " for a, b in g.edges\n", + " if (a == v and b in unexplored_neighbours)\n", + " or (b == v and a in unexplored_neighbours)\n", + " ]\n", + "\n", + " # pokud existují neprozkoumaní sousedé, obarveme je\n", + " if len(unexplored_neighbours) != 0:\n", + " self.play(\n", + " *[\n", + " g.vertices[q].animate.set_color(ORANGE)\n", + " for q in unexplored_neighbours\n", + " ],\n", + " *[\n", + " g.edges[e].animate.set_color(ORANGE)\n", + " for e in unexplored_neighbour_edges\n", + " ],\n", + " )\n", + "\n", + " explored.add(w)\n", + "\n", + " # animace přesunu do sousedního vrcholu\n", + " # má dvě části - nejprve začneme posun a poté změníme barvy (a flashneme)\n", + " \n", + " # používáme tu drobný hack - změna barvy posune hrany dopředu, což vypadá špatně\n", + " # proto na modrou obarvíme i vrchol v, což jej vyzvedne před ně\n", + " # elegantnější řešení je použít z_index, který si objasníme v příštím díle seriálu\n", + " self.play(\n", + " AnimationGroup(\n", + " position_object.animate.move_to(g.vertices[w]),\n", + " AnimationGroup(\n", + " Flash(g.vertices[w], color=BLUE, flash_radius=0.3),\n", + " g.edges[edge].animate.set_color(BLUE),\n", + " g.vertices[v].animate.set_color(BLUE),\n", + " g.vertices[w].animate.set_color(BLUE),\n", + " *[\n", + " g.vertices[q].animate.set_color(WHITE)\n", + " for q in unexplored_neighbours\n", + " if q != w\n", + " ],\n", + " *[\n", + " g.edges[(a, b)].animate.set_color(WHITE)\n", + " for (a, b) in unexplored_neighbour_edges\n", + " if (a, b) != edge\n", + " ],\n", + " ),\n", + " lag_ratio=0.45,\n", + " )\n", + " )\n", + "\n", + " dfs(w, position_object)\n", + " self.play(position_object.animate.move_to(g.vertices[v]))\n", + "\n", + " self.play(Write(g))\n", + "\n", + " # vyznačení startovního vrcholu\n", + " start_vertex = 0\n", + "\n", + " # objekt, který se posouvá podle toho, na jakém vrcholu jsme\n", + " position_object = (\n", + " Circle(fill_color=BLUE, fill_opacity=1, stroke_color=BLUE)\n", + " .move_to(g.vertices[start_vertex])\n", + " .scale(0.15)\n", + " )\n", + "\n", + " self.play(\n", + " Flash(g.vertices[start_vertex], color=BLUE, flash_radius=0.3),\n", + " g.vertices[start_vertex].animate.set_color(BLUE),\n", + " )\n", + "\n", + " self.add(position_object)\n", + "\n", + " # spuštění DFS\n", + " explored.add(start_vertex)\n", + " dfs(start_vertex, position_object)\n", + "\n", + " self.remove(position_object)\n", + " self.play(Unwrite(g))" + ] + }, + { + "cell_type": "markdown", + "id": "e6f13b54", + "metadata": {}, + "source": [ + "## Fibonacciho posloupnost" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fc2941bf", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh --disable_caching FibonacciSequence\n", + "\n", + "from random import *\n", + "\n", + "\n", + "class FibonacciSequence(MovingCameraScene):\n", + " def create_square(self, size):\n", + " \"\"\"Vytvoření čtverce dané velikosti.\"\"\"\n", + " return VGroup(Square(side_length=size), Tex(f\"${size}^2$\").scale(size))\n", + "\n", + " def get_camera_centering_animation(self, squares):\n", + " \"\"\"Animace k vycentrování kamery na čtverce.\"\"\"\n", + " h = squares.height * 1.5\n", + " return self.camera.frame.animate.set_height(h).move_to(squares)\n", + "\n", + " def construct(self):\n", + " squares = VGroup(self.create_square(1))\n", + "\n", + " self.camera.frame.move_to(squares).set_height(squares.height * 1.5)\n", + " self.camera.frame.save_state()\n", + "\n", + " self.play(Write(squares[0]))\n", + "\n", + " n = 7\n", + "\n", + " # vytvoření čtverců a jejich animování\n", + " a = 1\n", + " b = 1\n", + " directions = [RIGHT, UP, LEFT, DOWN]\n", + " for i in range(n):\n", + " b = b + a\n", + " a = b - a\n", + "\n", + " direction = directions[i % 4]\n", + "\n", + " new_square = self.create_square(a).next_to(squares, direction, buff=0)\n", + " squares.add(new_square)\n", + "\n", + " self.play(\n", + " FadeIn(new_square, shift=direction * a / 3),\n", + " self.get_camera_centering_animation(squares),\n", + " )\n", + "\n", + " dot = Dot().move_to(squares[0].get_corner(LEFT + UP)).scale(0.5)\n", + "\n", + " path = TracedPath(dot.get_center)\n", + "\n", + " def update_camera_position(camera):\n", + " \"\"\"Updater k pozicování kamery nad tečkou.\"\"\"\n", + " camera.move_to(dot.get_center())\n", + "\n", + " self.wait(1)\n", + "\n", + " # začátek spirály\n", + " self.play(\n", + " squares.animate.set_color(DARK_GRAY),\n", + " AnimationGroup(\n", + " self.camera.frame.animate.restore().move_to(dot),\n", + " Write(dot),\n", + " lag_ratio=0.5,\n", + " ),\n", + " )\n", + "\n", + " # nesmíme zapomenout TracedPath přidat do scény, aby byla vykreslována\n", + " self.add(path)\n", + " \n", + " self.camera.frame.add_updater(update_camera_position)\n", + "\n", + " center_dot = dot.copy()\n", + " self.add(center_dot)\n", + "\n", + " a = 0\n", + " b = 1\n", + " for i in range(n + 1):\n", + " # pole directions je definováno proti směru hodinových ručiček, proto\n", + " # sousední dvojice odpovídají bodům ve čtvercích, okolo kterých chceme otáčet\n", + " direction = directions[i % 4] + directions[(i + 1) % 4]\n", + " b = b + a\n", + " a = b - a\n", + "\n", + " # při každém čtvrtotočení zoomujeme zhruba o poměr sousedních fibonacciho\n", + " # čísel, což je phi (zlatý řez); číslo zmenšujeme, aby animace vypadala lépe\n", + " phi = (1 + 5 ** (1 / 2)) / 2\n", + " zoom_coefficient = phi * 0.9\n", + " \n", + " self.play(\n", + " Rotate(\n", + " dot,\n", + " about_point=squares[i].get_corner(direction),\n", + " angle=PI / 2,\n", + " ),\n", + " self.camera.frame.animate.scale(zoom_coefficient),\n", + " rate_func=linear,\n", + " )\n", + "\n", + " # po dotočení již nechceme držet kameru nad bodem, ani dále tracovat cestu\n", + " self.camera.frame.clear_updaters()\n", + " path.clear_updaters()\n", + "\n", + " self.play(self.get_camera_centering_animation(squares))\n", + " \n", + " self.wait(1)\n", + "\n", + " # fadeouty + hezké odspirálení\n", + " self.play(\n", + " FadeOut(squares),\n", + " FadeOut(dot),\n", + " AnimationGroup(\n", + " Unwrite(path, run_time=2),\n", + " AnimationGroup(Flash(center_dot, color=WHITE), FadeOut(center_dot)),\n", + " lag_ratio=0.9,\n", + " ),\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "8188912d", + "metadata": {}, + "source": [ + "## Langtonův mravenec" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cad4e39c", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh LangtonAnt\n", + "\n", + "from random import *\n", + "\n", + "\n", + "class Ant:\n", + " \"\"\"Třída mravence (pro čistší kód).\"\"\"\n", + "\n", + " deltas = [(-1, 0), (0, -1), (1, 0), (0, 1)]\n", + "\n", + " def __init__(self, position):\n", + " self.position = position\n", + " self.orientation = 0\n", + "\n", + " def __get_orientation_delta(self):\n", + " \"\"\"O kolik má mravenec posunout.\"\"\"\n", + " return self.deltas[self.orientation]\n", + "\n", + " def __rotate_by_delta(self, delta):\n", + " \"\"\"Otočení mravence do libovolného směru o násobek 90 stupňů.\"\"\"\n", + " self.orientation = (self.orientation + delta) % len(self.deltas)\n", + "\n", + " def rotate_left(self):\n", + " \"\"\"Otočení mravence doleva.\"\"\"\n", + " self.__rotate_by_delta(-1)\n", + "\n", + " def rotate_right(self):\n", + " \"\"\"Otočení mravence doprava.\"\"\"\n", + " self.__rotate_by_delta(1)\n", + "\n", + " def move_forward(self):\n", + " \"\"\"Posunutí mravence dopředu.\"\"\"\n", + " dx, dy = self.__get_orientation_delta()\n", + " self.position[0] += dx\n", + " self.position[1] += dy\n", + "\n", + " def update(self, states):\n", + " \"\"\"Posunutí a otočení mravence a upravení stavu.\"\"\"\n", + " x, y = self.position\n", + "\n", + " # znegování pole, na kterém mravenec stojí\n", + " states[y][x] = not states[y][x]\n", + "\n", + " # otočení\n", + " if states[y][x]:\n", + " self.rotate_right()\n", + " else:\n", + " self.rotate_left()\n", + "\n", + " # posunutí\n", + " self.move_forward()\n", + "\n", + "\n", + "class LangtonAnt(MovingCameraScene):\n", + " def construct(self):\n", + " n = 15\n", + " state = [[False for _ in range(n)] for _ in range(n)]\n", + " squares = [[Square() for _ in range(n)] for _ in range(n)]\n", + " squares_vgroup = VGroup(*[*sum(squares, [])]).arrange_in_grid(columns=n, buff=0)\n", + "\n", + " ant = Ant([n // 2, n // 2])\n", + " ant_object = (\n", + " SVGMobject(\"ant.svg\")\n", + " .set_height(squares_vgroup[0].height * 0.7)\n", + " .rotate(PI / 2)\n", + " )\n", + "\n", + " self.play(FadeIn(squares_vgroup), Write(ant_object))\n", + "\n", + " self.wait(1)\n", + "\n", + " step_count = 100\n", + "\n", + " # kolik iterací na začátku a na konci je pomalých\n", + " slow_start_iterations = 5\n", + " slow_end_iterations = 3\n", + "\n", + " # jak rychlé jsou animace\n", + " slow_run_time = 1\n", + " fast_run_time = 0.07\n", + "\n", + " for i in range(step_count):\n", + " x, y = ant.position\n", + "\n", + " new_color = state[y][x]\n", + " rect = squares[y][x]\n", + "\n", + " running_time = (\n", + " fast_run_time\n", + " if slow_start_iterations < i < step_count - slow_end_iterations\n", + " else slow_run_time\n", + " )\n", + "\n", + " # otáčení mravence\n", + " self.play(\n", + " Rotate(ant_object, PI / 2 * (1 if new_color else -1)),\n", + " run_time=running_time,\n", + " )\n", + "\n", + " # posunutí mravence\n", + " ant.update(state)\n", + " nx, ny = ant.position\n", + "\n", + " self.play(\n", + " rect.animate.set_fill(BLACK if new_color else WHITE, 1),\n", + " ant_object.animate.move_to(squares[ny][nx]),\n", + " self.camera.frame.animate.move_to(squares[ny][nx]),\n", + " run_time=running_time,\n", + " )\n", + "\n", + " self.wait(1)\n", + "\n", + " # zjistíme, které čtverce jsou aktuálně bílé\n", + " white_squares = VGroup()\n", + "\n", + " for i in range(n):\n", + " for j in range(n):\n", + " if state[i][j]:\n", + " white_squares.add(squares[i][j])\n", + "\n", + " # oddálíme a vycentrujeme na tyto čtverce\n", + " self.play(\n", + " self.camera.frame.animate.move_to(white_squares).set_height(\n", + " white_squares.height * 1.2\n", + " )\n", + " )\n", + "\n", + " self.play(FadeOut(squares_vgroup), FadeOut(ant_object))" + ] + } + ], + "metadata": { + "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.10.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/serial3.ipynb b/serial3.ipynb new file mode 100644 index 0000000..a45e03c --- /dev/null +++ b/serial3.ipynb @@ -0,0 +1,631 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "substantial-impact", + "metadata": {}, + "source": [ + "# Vítej!" + ] + }, + { + "cell_type": "markdown", + "id": "first-armenia", + "metadata": {}, + "source": [ + "Tento dokument obsahuje zdrojové kódy animací ke třetí sérii seriálu KSP. Před spouštěním opět nezapomeň Manim importovat spuštěním následujícího řádku!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e03b150c", + "metadata": {}, + "outputs": [], + "source": [ + "from manim import *" + ] + }, + { + "cell_type": "markdown", + "id": "45897e9c", + "metadata": {}, + "source": [ + "## `save` a `restore`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6aa5ba0f", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh SaveAndRestoreExample\n", + "\n", + "\n", + "class SaveAndRestoreExample(Scene):\n", + " def construct(self):\n", + " square = Square()\n", + "\n", + " # uložení toho, jak aktualně čtverec vypadá\n", + " square.save_state()\n", + "\n", + " self.play(Write(square))\n", + "\n", + " self.play(square.animate.set_fill(WHITE, 1))\n", + " self.play(square.animate.scale(1.5).rotate(PI / 4))\n", + " self.play(square.animate.set_color(BLUE))\n", + "\n", + " # navrácení do původního stavu\n", + " self.play(square.animate.restore())\n", + "\n", + " # nová animace!\n", + " self.play(Unwrite(square))" + ] + }, + { + "cell_type": "markdown", + "id": "88f6f285", + "metadata": {}, + "source": [ + "## Grafy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0906f798", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh GraphExample\n", + "\n", + "\n", + "class GraphExample(Scene):\n", + " def construct(self):\n", + " # graf očekává vrcholy a hrany v tomto tvaru\n", + " vertices = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]\n", + " edges = [\n", + " (1, 2),\n", + " (2, 3),\n", + " (3, 4),\n", + " (2, 4),\n", + " (2, 5),\n", + " (6, 5),\n", + " (1, 7),\n", + " (5, 7),\n", + " (2, 8),\n", + " (1, 9),\n", + " (10, 8),\n", + " (5, 11),\n", + " ]\n", + "\n", + " # layout_config používáme k tomu, aby byl algoritmus nastavující pozice vrcholů\n", + " # deterministický (ten defaultní potřebuje seed)\n", + " g = Graph(vertices, edges, layout_config={\"seed\": 0}).scale(1.6)\n", + "\n", + " self.play(Write(g))\n", + "\n", + " # graf obsahuje updatery, aby se hrany posouvaly s vrcholy\n", + " self.play(g.vertices[6].animate.shift((LEFT + DOWN) * 0.5))\n", + "\n", + " self.play(g.animate.shift(LEFT * 3))\n", + "\n", + " # grafy mohou rovněž obsahovat labely a mohou být uspořádány do jiných layoutů\n", + " # (pro ukázku všech viz link na dokumentaci třídy v seriálu)\n", + " h = Graph(vertices, edges, labels=True, layout=\"circular\").shift(RIGHT * 3)\n", + "\n", + " self.play(Write(h))\n", + "\n", + " # pokud chceme větší vrcholy i bez labelů, tak si je musíme obstarat manuálně\n", + " self.play(*[g.vertices[v].animate.scale(2.15) for v in g.vertices])\n", + "\n", + " # obarvíme vrchol 5 a jemu odpovídající hrany (může se hodit v jedné z úloh :)\n", + " v = 5\n", + " self.play(\n", + " Flash(g.vertices[v], color=RED, flash_radius=0.5),\n", + " g.vertices[v].animate.set_color(RED),\n", + " *[g.edges[e].animate.set_color(RED) for e in g.edges if v in e],\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98df83d3", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh GraphGenerationExample\n", + "\n", + "from random import *\n", + "import networkx as nx\n", + "\n", + "\n", + "class GraphGenerationExample(Scene):\n", + " def construct(self):\n", + " seed(0xDEADBEEF)\n", + "\n", + " n = 12 # počet vrcholů\n", + " p = 3 / n # pravděpodobnost, že mezi dvěma vrcholy je hrana\n", + "\n", + " # generujeme, dokud nemáme spojitý graf\n", + " graph = None\n", + " while graph is None or not nx.is_connected(graph):\n", + " graph = nx.generators.random_graphs.gnp_random_graph(n, p)\n", + "\n", + " g = (\n", + " Graph(graph.nodes, graph.edges, layout_config={\"seed\": 0})\n", + " .scale(2.2)\n", + " .rotate(-PI / 2)\n", + " )\n", + "\n", + " self.play(Write(g))" + ] + }, + { + "cell_type": "markdown", + "id": "bb5423f4", + "metadata": {}, + "source": [ + "## Kamera" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ab85ca2d", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh MovingCameraExample\n", + "\n", + "\n", + "class MovingCameraExample(MovingCameraScene):\n", + " def construct(self):\n", + " square = Square()\n", + "\n", + " self.play(Write(square))\n", + "\n", + " # uložíme si stav kamery, protože s ní následně budeme pracovat\n", + " self.camera.frame.save_state()\n", + "\n", + " # zoomneme tak, aby čtverec zaplňoval celý obraz (+ drobná mezera)\n", + " self.play(self.camera.frame.animate.set_height(square.height * 1.5))\n", + "\n", + " circle = Circle().next_to(square, LEFT)\n", + "\n", + " # posunutí kamery k novému objektu (kruhu)\n", + " self.play(\n", + " AnimationGroup(\n", + " self.camera.frame.animate.move_to(circle),\n", + " Write(circle),\n", + " lag_ratio=0.5,\n", + " )\n", + " )\n", + "\n", + " self.wait(0.5)\n", + "\n", + " # trošku odzoomujeme (zvětšení bude zabírat více scény)\n", + " self.play(self.camera.frame.animate.scale(1.3))\n", + "\n", + " triangle = Triangle().next_to(square, RIGHT)\n", + "\n", + " # posunutí kamery k novému objektu (trojúhelníku)\n", + " self.play(\n", + " AnimationGroup(\n", + " self.camera.frame.animate.move_to(triangle),\n", + " Write(triangle),\n", + " lag_ratio=0.5,\n", + " )\n", + " )\n", + "\n", + " self.wait(0.5)\n", + "\n", + " # navrácení kamery do původního stavu\n", + " self.play(self.camera.frame.animate.restore())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02c31d55", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh MovingCameraUpdaterExample\n", + "\n", + "from random import seed, uniform\n", + "\n", + "\n", + "class MovingCameraUpdaterExample(MovingCameraScene):\n", + " def construct(self):\n", + " seed(0xDEADBEEF)\n", + "\n", + " n = 11 ** 2\n", + "\n", + " circles = VGroup(\n", + " *[\n", + " Circle(radius=0.1)\n", + " .scale(uniform(0.5, 2))\n", + " .shift(UP * uniform(-3, 3) + RIGHT * uniform(-5, 5))\n", + " .set_color(WHITE)\n", + " for _ in range(n)\n", + " ]\n", + " )\n", + "\n", + " # kamerou budeme pozorovat kruh, který je v půlce\n", + " target = circles[n // 2]\n", + "\n", + " def update_curve(camera):\n", + " \"\"\"Updater, který udržuje kameru nad cílem.\"\"\"\n", + " camera.move_to(target.get_center())\n", + "\n", + " self.camera.frame.add_updater(update_curve)\n", + "\n", + " # POZOR!\n", + " # updatery fungují pouze na věci přidané na scénu\n", + " # self.camera.frame na scéně nejprve není, je ho potřeba přidat\n", + " self.add(self.camera.frame)\n", + "\n", + " self.play(FadeIn(circles))\n", + "\n", + " # animace pozicování kružnic a postupné Zoomování\n", + " scale_factor = 0.7\n", + "\n", + " self.play(\n", + " circles.animate.arrange_in_grid().set_color(RED),\n", + " self.camera.frame.animate.scale(scale_factor),\n", + " run_time=1.5,\n", + " )\n", + "\n", + " self.play(\n", + " circles.animate.arrange_in_grid(rows=5).set_color(GREEN),\n", + " self.camera.frame.animate.scale(scale_factor),\n", + " run_time=1.5,\n", + " )\n", + "\n", + " self.play(\n", + " circles.animate.arrange_in_grid(cols=14).set_color(BLUE),\n", + " self.camera.frame.animate.scale(scale_factor),\n", + " run_time=1.5,\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6cea874e", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh BackgroundColorExample\n", + "\n", + "\n", + "class BackgroundColorExample(MovingCameraScene):\n", + " def construct(self):\n", + " self.camera.background_color = WHITE\n", + " self.camera.frame.scale(0.6)\n", + "\n", + " square = Square(color=BLACK)\n", + "\n", + " self.play(Write(square))\n", + "\n", + " circle = Circle(color=BLACK).next_to(square, LEFT)\n", + " triangle = Triangle(color=BLACK).next_to(square, RIGHT)\n", + "\n", + " self.play(FadeIn(triangle, shift=RIGHT * 0.2), FadeIn(circle, shift=LEFT * 0.2))" + ] + }, + { + "cell_type": "markdown", + "id": "36661d3b", + "metadata": {}, + "source": [ + "## Rate funkce" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ad791dc", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh RateFunctionsExample\n", + "\n", + "# Kód pochází z Manimové dokumentace (modulo drobné úpravy)\n", + "# https://docs.manim.community/en/stable/reference/manim.utils.rate_functions.html\n", + "\n", + "\n", + "class RateFunctionsExample(Scene):\n", + " def construct(self):\n", + " line1 = Line(3 * LEFT, RIGHT).set_color(RED)\n", + " line2 = Line(3 * LEFT, RIGHT).set_color(GREEN)\n", + " line3 = Line(3 * LEFT, RIGHT).set_color(BLUE)\n", + " line4 = Line(3 * LEFT, RIGHT).set_color(ORANGE)\n", + "\n", + " lines = VGroup(line1, line2, line3, line4).arrange(DOWN, buff=0.8).move_to(LEFT * 2)\n", + "\n", + " dot1 = Dot().move_to(line1.get_start())\n", + " dot2 = Dot().move_to(line2.get_start())\n", + " dot3 = Dot().move_to(line3.get_start())\n", + " dot4 = Dot().move_to(line4.get_start())\n", + "\n", + " dots = VGroup(dot1, dot2, dot3, dot4)\n", + "\n", + " # pozor na podtržítka při psaní TeXu\n", + " label1 = Tex(\"smooth (default)\").next_to(line1, RIGHT, buff=0.5)\n", + " label2 = Tex(\"linear\").next_to(line2, RIGHT, buff=0.5)\n", + " label3 = Tex(\"there\\_and\\_back\").next_to(line3, RIGHT, buff=0.5)\n", + " label4 = Tex(\"rush\\_into\").next_to(line4, RIGHT, buff=0.5)\n", + "\n", + " labels = VGroup(label1, label2, label3, label4)\n", + "\n", + " self.play(Write(lines), FadeIn(dots), FadeIn(labels))\n", + "\n", + " # použití s animate syntaxem\n", + " self.play(\n", + " dot1.animate(rate_func=smooth).shift(RIGHT * 4),\n", + " dot2.animate(rate_func=linear).shift(RIGHT * 4),\n", + " dot3.animate(rate_func=there_and_back).shift(RIGHT * 4),\n", + " dot4.animate(rate_func=rush_into).shift(RIGHT * 4),\n", + " run_time=3,\n", + " )\n", + " \n", + " self.play(FadeOut(lines), FadeOut(dots))\n", + " \n", + " # použití s normálními animacemi\n", + " self.play(\n", + " Write(line1, rate_func=smooth),\n", + " Write(line2, rate_func=linear),\n", + " Write(line3, rate_func=there_and_back),\n", + " Write(line4, rate_func=rush_into),\n", + " run_time=3,\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26af99f1", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh RateFunctionList\n", + "\n", + "# Kód pochází z Manimové dokumentace (modulo drobné úpravy)\n", + "# https://docs.manim.community/en/stable/reference/manim.utils.rate_functions.html\n", + "\n", + "\n", + "class RateFunctionList(Scene):\n", + " def construct(self):\n", + " graphs = VGroup()\n", + " for k, v in rate_functions.__dict__.items():\n", + " if \"function\" in str(v):\n", + " try:\n", + " rate_func = v\n", + " plot = (\n", + " ParametricFunction(\n", + " lambda x: [x, rate_func(x), 0],\n", + " t_range=[0, 1, .01],\n", + " use_smoothing=False,\n", + " color=YELLOW,\n", + " )\n", + " .stretch_to_fit_width(1.5)\n", + " .stretch_to_fit_height(1)\n", + " )\n", + " plot_bg = SurroundingRectangle(plot).set_color(WHITE)\n", + " plot_title = (\n", + " Text(rate_func.__name__, weight=BOLD)\n", + " .scale(0.5)\n", + " .next_to(plot_bg, UP, buff=0.1)\n", + " )\n", + " graphs.add(VGroup(plot_bg, plot, plot_title))\n", + " except: # některé křivky jsou parametrické (a některé rozbité)\n", + " pass\n", + " graphs.arrange_in_grid(cols=8)\n", + " graphs.height = config.frame_height\n", + " graphs.width = config.frame_width\n", + " graphs.move_to(ORIGIN).scale(0.9)\n", + " \n", + " text = Tex(f\"Manim rate functions (v0.13.1)\").scale(1.4).next_to(graphs, UP, buff=0.5)\n", + " \n", + " self.add(VGroup(graphs, text).move_to(ORIGIN))" + ] + }, + { + "cell_type": "markdown", + "id": "fd7a5d4c", + "metadata": {}, + "source": [ + "## Krokování" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f46b6587", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh --save_sections NextSectionExample\n", + "\n", + "class NextSectionExample(Scene):\n", + " def construct(self):\n", + " shapes = VGroup(Circle(), Square(), Triangle()).arrange()\n", + " \n", + " # jméno sekce je nepovinné a nemusí být unikátní\n", + " # skip_animations animování sekce přeskočí\n", + " self.next_section(\"vykreslení kruhu\", skip_animations=True)\n", + " \n", + " self.play(Write(shapes[0]))\n", + " \n", + " self.next_section(\"vykreslení čtverce\")\n", + " \n", + " self.play(Write(shapes[1]))\n", + " \n", + " self.next_section(\"vykreslení trojúhelníku\")\n", + " \n", + " self.play(Write(shapes[2]))" + ] + }, + { + "cell_type": "markdown", + "id": "98daca2b", + "metadata": {}, + "source": [ + "## Kostry úloh" + ] + }, + { + "cell_type": "markdown", + "id": "8746ae11", + "metadata": {}, + "source": [ + "### Grafový algoritmus [5b]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18fb5e35", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh GraphAlgorithm\n", + "\n", + "\n", + "class GraphAlgorithm(Scene):\n", + " def construct(self):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "4de93509", + "metadata": {}, + "source": [ + "### Fibonacciho posloupnost [5b]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49bb1f67", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh --disable_caching FibonacciSequence\n", + "\n", + "\n", + "class FibonacciSequence(MovingCameraScene):\n", + " def construct(self):\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d647e5f", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh --disable_caching TracePathExample\n", + "\n", + " \n", + "class TracePathExample(Scene):\n", + " def construct(self):\n", + " dot = Dot().shift(LEFT)\n", + " \n", + " self.play(Write(dot))\n", + " \n", + " # objekt TracedPath přijímá funkci, které se opakovaně ptá na pozici objektu, který sleduje\n", + " # proto používáme dot.get_center, která vrací aktuální pozici tečky kterou tracujeme\n", + " path = TracedPath(dot.get_center)\n", + " \n", + " # nesmíme zapomenout cestu přidat do scény, aby byla vykreslována!\n", + " self.add(path)\n", + " \n", + " self.play(Rotate(dot, about_point=ORIGIN))\n", + " \n", + " self.play(dot.animate.shift(UP))\n", + " self.play(dot.animate.shift(LEFT * 2))\n", + " self.play(dot.animate.shift(DOWN))\n", + " \n", + " # pro zkončení tracování můžeme použít clear_updaters\n", + " path.clear_updaters()\n", + " \n", + " self.play(dot.animate.shift(RIGHT * 2))" + ] + }, + { + "cell_type": "markdown", + "id": "159f8abb", + "metadata": {}, + "source": [ + "### Langtonův mravenec [5b]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d95d2328", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh LangtonAnt\n", + "\n", + "\n", + "class LangtonAnt(MovingCameraScene):\n", + " def construct(self):\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "386b5d61", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh SVGExample\n", + "\n", + "\n", + "class SVGExample(Scene):\n", + " def construct(self):\n", + " image = SVGMobject(\"ant.svg\")\n", + "\n", + " self.play(Write(image))\n", + "\n", + " self.play(image.animate.set_color(RED).scale(1.75))\n", + "\n", + " self.play(Rotate(image, TAU)) # tau = 2 pi\n", + "\n", + " self.play(FadeOut(image))" + ] + } + ], + "metadata": { + "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.10.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/serial4-reseni.ipynb b/serial4-reseni.ipynb new file mode 100644 index 0000000..56d3aa9 --- /dev/null +++ b/serial4-reseni.ipynb @@ -0,0 +1,446 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "36cee816", + "metadata": {}, + "source": [ + "# Řešení 3. série" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efe16561", + "metadata": {}, + "outputs": [], + "source": [ + "from manim import *" + ] + }, + { + "cell_type": "markdown", + "id": "36ec08cc", + "metadata": {}, + "source": [ + "## Simulace binomického rozložení " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7a3da39", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh --disable_caching BinomialDistributionSimulation\n", + "\n", + "from random import choice, seed\n", + "\n", + "\n", + "class MoveAndFade(Animation):\n", + " def __init__(self, mobject: Mobject, path: VMobject, **kwargs):\n", + " self.path = path\n", + " self.original = mobject.copy()\n", + " super().__init__(mobject, **kwargs)\n", + "\n", + " def interpolate_mobject(self, alpha: float) -> None:\n", + " point = self.path.point_from_proportion(self.rate_func(alpha))\n", + "\n", + " # tohle není úplně čisté, jelikož pokaždé vytváříme nový objekt\n", + " # je to kvůli tomu, že obj.fade() nenastavuje průhlednost ale přidává jí\n", + " self.mobject.become(self.original.copy()).move_to(point).fade(alpha)\n", + "\n", + "\n", + "class BinomialDistributionSimulation(Scene):\n", + " def construct(self):\n", + " seed(0xDEADBEEF2) # hezčí vstupy :)\n", + "\n", + " radius = 0.13\n", + " x_spacing = radius * 1.5\n", + " y_spacing = 4 * radius\n", + "\n", + " n = 9\n", + " pyramid = VGroup()\n", + " pyramid_values = []\n", + "\n", + " for i in range(1, n + 1):\n", + " row = VGroup()\n", + "\n", + " for j in range(i):\n", + " obj = Dot()\n", + "\n", + " # if it's the last row, make the rows numbers instead\n", + " if i == n:\n", + " obj = Tex(\"0\")\n", + " pyramid_values.append(0)\n", + "\n", + " row.add(obj)\n", + "\n", + " row.arrange(buff=2 * x_spacing)\n", + "\n", + " if len(pyramid) != 0:\n", + " row.move_to(pyramid[-1]).shift(DOWN * y_spacing)\n", + "\n", + " pyramid.add(row)\n", + "\n", + " pyramid.move_to(RIGHT * 3.4)\n", + "\n", + " x_values = np.arange(-n // 2 + 1, n // 2 + 1, 1)\n", + "\n", + " def create_graph(x_values, y_values):\n", + " y_values_all = list(range(0, (max(y_values) or 1) + 1))\n", + "\n", + " axes = (\n", + " Axes(\n", + " x_range=[-n // 2 + 1, n // 2, 1],\n", + " y_range=[0, max(y_values) or 1, 1],\n", + " x_axis_config={\"numbers_to_include\": x_values},\n", + " tips=False,\n", + " )\n", + " .scale(0.45)\n", + " .shift(LEFT * 3.0)\n", + " )\n", + "\n", + " graph = axes.plot_line_graph(x_values=x_values, y_values=y_values)\n", + "\n", + " return graph, axes\n", + "\n", + " graph, axes = create_graph(x_values, pyramid_values)\n", + "\n", + " self.play(Write(axes), Write(pyramid), Write(graph), run_time=1.5)\n", + "\n", + " for iteration in range(120):\n", + " circle = (\n", + " Circle(fill_opacity=1, stroke_opacity=0)\n", + " .scale(radius)\n", + " .next_to(pyramid[0][0], UP, buff=0)\n", + " )\n", + "\n", + " run_time = (\n", + " 0.5\n", + " if iteration == 0\n", + " else 0.1\n", + " if iteration == 1\n", + " else 0.02\n", + " if iteration < 20\n", + " else 0.003\n", + " )\n", + "\n", + " self.play(FadeIn(circle, shift=DOWN * 0.5), run_time=run_time * 2)\n", + "\n", + " x = 0\n", + " for i in range(1, n):\n", + " next_position = choice([0, 1])\n", + " x += next_position\n", + "\n", + " dir = LEFT if next_position == 0 else RIGHT\n", + "\n", + " circle_center = circle.get_center()\n", + "\n", + " # if it's not the last row, make the animation regular\n", + " if i != n - 1:\n", + " b = CubicBezier(\n", + " circle_center,\n", + " circle_center + dir * x_spacing,\n", + " circle_center + dir * x_spacing + DOWN * y_spacing / 2,\n", + " circle.copy().next_to(pyramid[i][x], UP, buff=0).get_center(),\n", + " )\n", + "\n", + " self.play(\n", + " MoveAlongPath(circle, b, rate_func=rate_functions.ease_in_quad),\n", + " run_time=run_time,\n", + " )\n", + "\n", + " # if it is, fade the circle and increment the number\n", + " else:\n", + " b = CubicBezier(\n", + " circle_center,\n", + " circle_center + dir * x_spacing,\n", + " circle_center + dir * x_spacing + DOWN * y_spacing / 2,\n", + " pyramid[i][x].get_center(),\n", + " )\n", + "\n", + " pyramid_values[x] += 1\n", + "\n", + " n_graph, n_axes = create_graph(x_values, pyramid_values)\n", + "\n", + " self.play(\n", + " AnimationGroup(\n", + " AnimationGroup(\n", + " MoveAndFade(\n", + " circle, b, rate_func=rate_functions.ease_in_quad\n", + " ),\n", + " run_time=run_time,\n", + " ),\n", + " AnimationGroup(\n", + " pyramid[i][x]\n", + " .animate(run_time=run_time)\n", + " .become(\n", + " Tex(str(pyramid_values[x])).move_to(pyramid[i][x])\n", + " ),\n", + " graph.animate.become(n_graph),\n", + " axes.animate.become(n_axes),\n", + " run_time=run_time,\n", + " ),\n", + " lag_ratio=0.3,\n", + " )\n", + " )\n", + "\n", + " self.play(FadeOut(axes), FadeOut(pyramid), FadeOut(graph), run_time=1)\n" + ] + }, + { + "cell_type": "markdown", + "id": "e6f13b54", + "metadata": {}, + "source": [ + "## 3D Game of Life" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fc2941bf", + "metadata": {}, + "outputs": [], + "source": [ + "from manim import *\n", + "from random import random, seed\n", + "from enum import Enum\n", + "\n", + "# https://softologyblog.wordpress.com/2019/12/28/3d-cellular-automata-3/\n", + "\n", + "class Grid:\n", + " class ColorType(Enum):\n", + " FROM_COORDINATES = 0\n", + " FROM_PALETTE = 1\n", + "\n", + " def __init__(self, scene, grid_size, survives_when, revives_when, state_count=2, size=1, palette=[\"#000b5e\", \"#001eff\"], color_type=ColorType.FROM_PALETTE):\n", + " self.grid = {}\n", + " self.scene = scene\n", + " self.grid_size = grid_size\n", + " self.size = size\n", + " self.survives_when = survives_when\n", + " self.revives_when = revives_when\n", + " self.state_count = state_count\n", + " self.palette = palette\n", + " self.color_type = color_type\n", + "\n", + " self.bounding_box = Cube(side_length = self.size, color=GRAY, fill_opacity=0.05)\n", + " self.scene.add(self.bounding_box)\n", + "\n", + " def fadeOut(self):\n", + " self.scene.play(\n", + " FadeOut(self.bounding_box),\n", + " *[FadeOut(self.grid[index][0]) for index in self.grid],\n", + " )\n", + "\n", + " def __index_to_position(self, index):\n", + " \"\"\"Convert the index of a cell to its position in 3D.\"\"\"\n", + " dirs = [RIGHT, UP, OUT]\n", + "\n", + " # pozor!\n", + " # nemůžeme k originu jen tak přičítat, jelikož to nevytváří nové objekty\n", + " # tím pádem bychom pořád měnili ten samý objekt, což nechceme -- proto list()\n", + " result = list(ORIGIN)\n", + " for dir, value in zip(dirs, index):\n", + " result += ((value - (self.grid_size- 1) / 2) /\n", + " self.grid_size) * dir * self.size\n", + "\n", + " return result\n", + "\n", + " def __get_new_cell(self, index):\n", + " \"\"\"Create a new cell\"\"\"\n", + " cell = (Cube(side_length=1/self.grid_size * self.size, color=BLUE, fill_opacity=1).move_to(\n", + " self.__index_to_position(index)\n", + " ), self.state_count - 1)\n", + "\n", + " self.__update_cell_color(index, *cell)\n", + "\n", + " return cell\n", + "\n", + " def __return_neighbouring_cell_coordinates(self, index):\n", + " \"\"\"Return the coordinates of the neighbourhood of a given index.\"\"\"\n", + " neighbourhood = set()\n", + " for dx in range(-1, 1 + 1):\n", + " for dy in range(-1, 1 + 1):\n", + " for dz in range(-1, 1 + 1):\n", + " if dx == 0 and dy == 0 and dz == 0:\n", + " continue\n", + "\n", + " nx = index[0] + dx\n", + " ny = index[1] + dy\n", + " nz = index[2] + dz\n", + "\n", + " if nx < 0 or nx >= self.grid_size or ny < 0 or ny >= self.grid_size or nz < 0 or nz >= self.grid_size:\n", + " continue\n", + "\n", + " neighbourhood.add((nx, ny, nz))\n", + "\n", + " return neighbourhood\n", + "\n", + " def __count_neighbours(self, index):\n", + " \"\"\"Return the number of neighbouring cells for a given index (excluding itself).\"\"\"\n", + " total = 0\n", + " for neighbour_index in self.__return_neighbouring_cell_coordinates(index):\n", + " if neighbour_index in self.grid:\n", + " total += 1\n", + "\n", + " return total\n", + "\n", + " def __return_possible_cell_change_indexes(self):\n", + " \"\"\"Return the indexes of all possible cells that could change.\"\"\"\n", + " changes = set()\n", + " for index in self.grid:\n", + " changes |= self.__return_neighbouring_cell_coordinates(index).union({\n", + " index})\n", + " return changes\n", + "\n", + " def toggle(self, index):\n", + " \"\"\"Toggle a given cell.\"\"\"\n", + " if index in self.grid:\n", + " self.scene.remove(self.grid[index][0])\n", + " del self.grid[index]\n", + " else:\n", + " self.grid[index] = self.__get_new_cell(index)\n", + " self.scene.add(self.grid[index][0])\n", + "\n", + " def __update_cell_color(self, index, cell, age):\n", + " if self.color_type == self.ColorType.FROM_PALETTE:\n", + " state_colors = color_gradient(self.palette, self.state_count - 1)\n", + "\n", + " cell.set_color(state_colors[age - 1])\n", + " else:\n", + " def coordToHex(n):\n", + " return hex(int(n * (256 / self.grid_size)))[2:].ljust(2, \"0\")\n", + "\n", + " cell.set_color(f\"#{coordToHex(index[0])}{coordToHex(index[1])}{coordToHex(index[2])}\")\n", + "\n", + " def do_iteration(self):\n", + " new_grid = {}\n", + " something_changed = False\n", + "\n", + " for index in self.__return_possible_cell_change_indexes():\n", + " neighbours = self.__count_neighbours(index)\n", + "\n", + " # alive rules\n", + " if index in self.grid:\n", + " cell, age = self.grid[index]\n", + "\n", + " # always decrease age\n", + " if age != 1:\n", + " age -= 1\n", + " something_changed = True\n", + "\n", + " # survive if within range or age isn't 1\n", + " if neighbours in self.survives_when or age != 1:\n", + " self.__update_cell_color(index, cell, age)\n", + " new_grid[index] = (cell, age)\n", + " else:\n", + " self.scene.remove(self.grid[index][0])\n", + " something_changed = True\n", + "\n", + " # dead rules\n", + " else:\n", + " # revive if within range\n", + " if neighbours in self.revives_when:\n", + " new_grid[index] = self.__get_new_cell(index)\n", + " self.scene.add(new_grid[index][0])\n", + " something_changed = True\n", + "\n", + " self.grid = new_grid\n", + "\n", + " return something_changed\n", + "\n", + "class GOLFirst(ThreeDScene):\n", + " def construct(self):\n", + " seed(0xDEADBEEF)\n", + "\n", + " self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)\n", + " self.begin_ambient_camera_rotation(rate=-0.20)\n", + "\n", + " grid_size = 16\n", + " size=3.5\n", + " grid = Grid(self, grid_size, [4, 5], [5], state_count=2, size=size, color_type=Grid.ColorType.FROM_COORDINATES)\n", + "\n", + " for i in range(grid_size):\n", + " for j in range(grid_size):\n", + " for k in range(grid_size):\n", + " if random() < 0.2:\n", + " grid.toggle((i, j, k))\n", + "\n", + " grid.fadeIn()\n", + "\n", + " self.wait(1)\n", + "\n", + " for i in range(50):\n", + " something_changed = grid.do_iteration()\n", + "\n", + " if not something_changed:\n", + " break\n", + "\n", + " self.wait(0.2)\n", + "\n", + " self.wait(2)\n", + "\n", + " grid.fadeOut()\n", + "\n", + "\n", + "class GOL(ThreeDScene):\n", + " def construct(self):\n", + " seed(0xDEADBEEF)\n", + "\n", + " self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)\n", + " self.begin_ambient_camera_rotation(rate=0.15)\n", + "\n", + " grid_size = 16\n", + " size=3.5\n", + "\n", + " # decay grid = Grid(self, grid_size, [1, 4, 8, 11] + list(range(13, 26 + 1)), list(range(13, 26 + 1)), state_count=5, size=size, color_type=Grid.ColorType.FROM_PALETTE)\n", + " grid = Grid(self, grid_size, [2, 6, 9], [4, 6, 8, 9], state_count=10, size=size, color_type=Grid.ColorType.FROM_PALETTE)\n", + "\n", + " for i in range(grid_size):\n", + " for j in range(grid_size):\n", + " for k in range(grid_size):\n", + " if random() < 0.3:\n", + " grid.toggle((i, j, k))\n", + "\n", + " self.wait(2)\n", + "\n", + " for i in range(70):\n", + " something_changed = grid.do_iteration()\n", + "\n", + " if not something_changed:\n", + " break\n", + "\n", + " self.wait(0.1)\n", + "\n", + " self.wait(2)\n", + "\n", + " grid.fadeOut()\n" + ] + } + ], + "metadata": { + "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.10.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/serial4.ipynb b/serial4.ipynb new file mode 100644 index 0000000..c1aca33 --- /dev/null +++ b/serial4.ipynb @@ -0,0 +1,675 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "substantial-impact", + "metadata": {}, + "source": [ + "# Vítej!" + ] + }, + { + "cell_type": "markdown", + "id": "first-armenia", + "metadata": {}, + "source": [ + "Tento dokument obsahuje zdrojové kódy animací ke čtvrté sérii seriálu KSP. Před spouštěním opět nezapomeň Manim importovat spuštěním následujícího řádku!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e03b150c", + "metadata": {}, + "outputs": [], + "source": [ + "from manim import *" + ] + }, + { + "cell_type": "markdown", + "id": "45897e9c", + "metadata": {}, + "source": [ + "## Booleovské operace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6aa5ba0f", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh BooleanOperations\n", + "\n", + "\n", + "class BooleanOperations(Scene):\n", + " def construct(self):\n", + "\n", + " circle = Circle(fill_opacity=0.75, color=RED).scale(2).shift(LEFT * 1.5)\n", + " square = Square(fill_opacity=0.75, color=GREEN).scale(2).shift(RIGHT * 1.5)\n", + "\n", + " group = VGroup(circle, square)\n", + "\n", + " self.play(Write(group))\n", + "\n", + " self.play(group.animate.scale(0.5).shift(UP * 1.6))\n", + "\n", + " union = Union(circle, square, fill_opacity=1, color=BLUE)\n", + "\n", + " # postupně voláme Union(), Intersection() a Difference()\n", + " for operation, position, name in zip(\n", + " [Intersection, Union, Difference],\n", + " [LEFT * 3.3, ORIGIN, RIGHT * 4.5],\n", + " [\"Průnik\", \"Sjednocení\", \"Rozdíl\"],\n", + " ):\n", + " result = operation(circle, square, fill_opacity=1, color=DARK_BLUE)\n", + " result_position = DOWN * 1.3 + position\n", + " \n", + " label = Tex(name).move_to(result_position).scale(0.8)\n", + " \n", + " self.play(\n", + " AnimationGroup(\n", + " FadeIn(result),\n", + " result.animate.move_to(result_position),\n", + " FadeIn(label),\n", + " lag_ratio=0.5,\n", + " )\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "3da40017", + "metadata": {}, + "source": [ + "## Vlastní objekty" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf1fb3b5", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh StackExample\n", + "\n", + "\n", + "class Stack(VMobject):\n", + " def __init__(self, size, *args, **kwargs):\n", + " # inicializace VGroup objektu\n", + " super().__init__(**kwargs)\n", + "\n", + " self.squares = VGroup()\n", + " self.labels = VGroup()\n", + " self.index = 0\n", + " self.pointer = Arrow(ORIGIN, UP * 1.2)\n", + "\n", + " for _ in range(size):\n", + " self.squares.add(Square(side_length=0.8))\n", + "\n", + " self.squares.arrange(buff=0.15)\n", + "\n", + " self.pointer.next_to(self.squares[0], DOWN)\n", + " self.add()\n", + "\n", + " # DŮLEŽITÉ - přidáme do objektu všechny podobjekty!\n", + " self.add(self.squares, self.labels, self.pointer)\n", + "\n", + " def __get_index_rectangle_color(self):\n", + " \"\"\"Vrátí barvu aktuálního obdelníků stacku.\"\"\"\n", + " return self.squares[self.index].get_color()\n", + "\n", + " def __create_label(self, element):\n", + " \"\"\"Vytvoření labelu daného prvku (podle barvy a rozměrů čtverců stacku).\"\"\"\n", + " return (\n", + " Tex(str(element))\n", + " .scale(self.squares[0].height)\n", + " .set_color(self.__get_index_rectangle_color())\n", + " )\n", + "\n", + " def push(self, element):\n", + " \"\"\"Přidá prvek do zásobníku. Vrátí odpovídající animace.\"\"\"\n", + " self.labels.add(self.__create_label(element).move_to(self.squares[self.index]))\n", + " self.index += 1\n", + "\n", + " return AnimationGroup(\n", + " FadeIn(self.labels[-1]),\n", + " self.pointer.animate.next_to(self.squares[self.index], DOWN),\n", + " Indicate(\n", + " self.squares[self.index - 1], color=self.__get_index_rectangle_color()\n", + " ),\n", + " )\n", + "\n", + " def pop(self):\n", + " \"\"\"Odebere prvek ze zásobníku. Vrátí odpovídající animace.\"\"\"\n", + " label = self.labels[-1]\n", + " self.labels.remove(label)\n", + " self.index -= 1\n", + "\n", + " return AnimationGroup(\n", + " FadeOut(label),\n", + " self.pointer.animate.next_to(self.squares[self.index], DOWN),\n", + " Indicate(\n", + " self.squares[self.index],\n", + " color=self.__get_index_rectangle_color(),\n", + " scale_factor=1 / 1.2,\n", + " ),\n", + " )\n", + "\n", + " def clear(self):\n", + " \"\"\"Vyčistí zásobník. Vrátí odpovídající animaci.\"\"\"\n", + " result = AnimationGroup(*[self.pop() for _ in range(self.index)], lag_ratio=0)\n", + "\n", + " self.index = 0\n", + "\n", + " return result\n", + "\n", + "\n", + "class StackExample(Scene):\n", + " def construct(self):\n", + " stack = Stack(10)\n", + "\n", + " # na objekt fungují správně libovolné animace\n", + " self.play(Write(stack))\n", + "\n", + " self.wait(0.5)\n", + "\n", + " for i in range(5):\n", + " self.play(stack.push(i))\n", + "\n", + " self.play(stack.pop())\n", + "\n", + " self.wait(0.5)\n", + "\n", + " # můžeme používat i animate syntax!\n", + " # měnění barvy se rekurzivně aplikuje na všechny podobjekty\n", + " self.play(stack.animate.scale(1.3).set_color(BLUE))\n", + "\n", + " self.wait(0.5)\n", + "\n", + " for i in range(2):\n", + " self.play(stack.push(i))\n", + "\n", + " self.play(stack.pop())\n", + "\n", + " self.play(stack.clear())\n", + "\n", + " self.play(FadeOut(stack))" + ] + }, + { + "cell_type": "markdown", + "id": "88f6f285", + "metadata": {}, + "source": [ + "## Jiné grafy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0906f798", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh GraphExample\n", + "\n", + "from math import sin\n", + "\n", + "\n", + "class GraphExample(Scene):\n", + " def construct(self):\n", + " # osy - rozmezí a značení os x, y\n", + " axes = Axes(x_range=[-5, 5], y_range=[-3, 7])\n", + " labels = axes.get_axis_labels(x_label=\"x\", y_label=\"y\")\n", + "\n", + " def f1(x):\n", + " return x ** 2\n", + "\n", + " def f2(x):\n", + " return sin(x)\n", + "\n", + " # objekty vykreslených funkcí\n", + " g1 = axes.plot(f1, color=RED)\n", + " g2 = axes.plot(f2, color=BLUE)\n", + "\n", + " self.play(Write(axes), Write(labels))\n", + "\n", + " self.play(AnimationGroup(Write(g1), Write(g2), lag_ratio=0.5))\n", + "\n", + " self.play(Unwrite(axes), Unwrite(labels), Unwrite(g1), Unwrite(g2))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84978263", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh DiscontinuousGraphExample\n", + "\n", + "from math import sin\n", + "\n", + "\n", + "class DiscontinuousGraphExample(Scene):\n", + " def construct(self):\n", + " axes = Axes(x_range=[-5, 5], y_range=[-3, 7])\n", + " labels = axes.get_axis_labels(x_label=\"x\", y_label=\"y\")\n", + "\n", + " def f(x):\n", + " return 1 / x\n", + "\n", + " g_bad = axes.plot(f, color=RED)\n", + "\n", + " # rozdělení na dvě části podle hodnot x\n", + " g_left = axes.plot(f, x_range=[-5, -0.1], color=GREEN)\n", + " g_right = axes.plot(f, x_range=[0.1, 5], color=GREEN)\n", + "\n", + " self.play(Write(axes), Write(labels))\n", + "\n", + " self.play(Write(g_bad))\n", + " self.play(FadeOut(g_bad))\n", + "\n", + " self.play(AnimationGroup(Write(g_left), Write(g_right), lag_ratio=0.5))\n", + "\n", + " self.play(Unwrite(axes), Unwrite(labels), Unwrite(g_left), Unwrite(g_right))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "521d3286", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh ParametricGraphExample\n", + "\n", + "from math import sin, cos\n", + "\n", + "\n", + "class ParametricGraphExample(Scene):\n", + " def construct(self):\n", + " axes = Axes(x_range=[-10, 10], y_range=[-5, 5])\n", + " labels = axes.get_axis_labels(x_label=\"x\", y_label=\"y\")\n", + "\n", + " def f1(t):\n", + " \"\"\"Parametrická funkce kružnice.\"\"\"\n", + " return (cos(t) * 3 - 4.5, sin(t) * 3)\n", + "\n", + " def f2(t):\n", + " \"\"\"Parametrická funkce <3.\"\"\"\n", + " return (\n", + " 0.2 * (16 * (sin(t)) ** 3) + 4.5,\n", + " 0.2 * (13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t)),\n", + " )\n", + "\n", + " # objekty vykreslených funkcí\n", + " # místo axes.plot používáme axes.plot_parametric_curve\n", + " # parametr t_range určuje, jaké je rozmezí parametru t\n", + " g1 = axes.plot_parametric_curve(f1, color=RED, t_range=[0, 2 * PI])\n", + " g2 = axes.plot_parametric_curve(f2, color=BLUE, t_range=[-PI, PI])\n", + "\n", + " self.play(Write(axes), Write(labels))\n", + "\n", + " self.play(AnimationGroup(Write(g1), Write(g2), lag_ratio=0.5))\n", + "\n", + " self.play(Unwrite(axes), Unwrite(labels), Unwrite(g1), Unwrite(g2))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0e61a1b", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh LineGraphExample\n", + "\n", + "from random import random, seed\n", + "\n", + "\n", + "class LineGraphExample(Scene):\n", + " def construct(self):\n", + " seed(0xDEADBEEF2) # hezčí hodnoty :P\n", + "\n", + " # hodnoty ke grafování (x a y)\n", + " # u np.arange(l, r, step) vrátí pole hodnot od l do r (nevčetně) s kroky velikosti step\n", + " x_values = np.arange(-1, 1 + 0.25, 0.25)\n", + " y_values = [random() for _ in x_values]\n", + "\n", + " # osy (tentokrát s čísly)\n", + " axes = Axes(\n", + " x_range=[-1, 1, 0.25],\n", + " y_range=[-0.1, 1, 0.25],\n", + " # nastavení čísel - hodnoty a počet desetinných míst\n", + " x_axis_config={\"numbers_to_include\": x_values},\n", + " y_axis_config={\"numbers_to_include\": np.arange(0, 1, 0.25)},\n", + " axis_config={\"decimal_number_config\": {\"num_decimal_places\": 2}},\n", + " )\n", + "\n", + " labels = axes.get_axis_labels(x_label=\"x\", y_label=\"y\")\n", + "\n", + " # místo axes.plot používáme axes.plot_line_graph\n", + " graph = axes.plot_line_graph(x_values=x_values, y_values=y_values)\n", + "\n", + " self.play(Write(axes), Write(labels))\n", + "\n", + " self.play(Write(graph), run_time=2)\n", + "\n", + " self.play(Unwrite(axes), Unwrite(labels), Unwrite(graph))" + ] + }, + { + "cell_type": "markdown", + "id": "bb5423f4", + "metadata": {}, + "source": [ + "## 3D operace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ab85ca2d", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh Axes3DExample\n", + "\n", + "\n", + "class Axes3DExample(ThreeDScene):\n", + " def construct(self):\n", + " # 3D osy\n", + " axes = ThreeDAxes()\n", + "\n", + " x_label = axes.get_x_axis_label(Tex(\"x\"))\n", + " y_label = axes.get_y_axis_label(Tex(\"y\")).shift(UP * 1.8)\n", + "\n", + " # 3D varianta Dot() objektu\n", + " dot = Dot3D()\n", + "\n", + " # zmenšení zoomu, abychom viděli osy\n", + " self.set_camera_orientation(zoom=0.5)\n", + "\n", + " self.play(FadeIn(axes), FadeIn(dot), FadeIn(x_label), FadeIn(y_label))\n", + "\n", + " self.wait(0.5)\n", + "\n", + " # animace posunutí kamery tak, aby byly osy dobře vidět\n", + " self.move_camera(phi=75 * DEGREES, theta=30 * DEGREES, zoom=1, run_time=1.5)\n", + "\n", + " # vestavěný updater, který kameru začne rotovat (aby na scénu bylo lépe vidět)\n", + " self.begin_ambient_camera_rotation(rate=0.15)\n", + "\n", + " # jedna tečka za každý směr\n", + " upDot = dot.copy().set_color(RED)\n", + " rightDot = dot.copy().set_color(BLUE)\n", + " outDot = dot.copy().set_color(GREEN)\n", + "\n", + " self.wait(1)\n", + "\n", + " self.play(\n", + " upDot.animate.shift(UP),\n", + " rightDot.animate.shift(RIGHT),\n", + " outDot.animate.shift(OUT),\n", + " )\n", + "\n", + " self.wait(2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efc147d5", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh Rotation3DExample\n", + "\n", + "\n", + "class Rotation3DExample(ThreeDScene):\n", + " def construct(self):\n", + " # přidání jednoduché základní krychle\n", + " cube = Cube(side_length=3, fill_opacity=1)\n", + "\n", + " self.begin_ambient_camera_rotation(rate=0.3)\n", + "\n", + " # posunutí orientace kamery bez animace\n", + " self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)\n", + "\n", + " self.play(Write(cube), run_time=2)\n", + "\n", + " self.wait(3)\n", + "\n", + " self.play(Unwrite(cube), run_time=2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "127ccd71", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh Basic3DExample\n", + "\n", + "\n", + "class Basic3DExample(ThreeDScene):\n", + " def construct(self):\n", + " # přidání jednoduché základní krychle\n", + " cube = Cube(side_length=3, fill_opacity=0.5)\n", + "\n", + " self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)\n", + "\n", + " self.play(FadeIn(cube))\n", + "\n", + " for axis in [RIGHT, UP, OUT]:\n", + " self.play(Rotate(cube, PI / 2, about_point=ORIGIN, axis=axis))\n", + "\n", + " self.play(FadeOut(cube))" + ] + }, + { + "cell_type": "markdown", + "id": "98daca2b", + "metadata": {}, + "source": [ + "## Kostry úloh" + ] + }, + { + "cell_type": "markdown", + "id": "8746ae11", + "metadata": {}, + "source": [ + "### Simulace binomického rozložení [6b]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18fb5e35", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh BinomialDistributionSimulation\n", + "\n", + "\n", + "class BinomialDistributionSimulation(Scene):\n", + " def construct(self):\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7139b0d", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh BezierExample\n", + "\n", + "from random import choice, seed\n", + "\n", + "\n", + "class MoveAndFade(Animation):\n", + " def __init__(self, mobject: Mobject, path: VMobject, **kwargs):\n", + " self.path = path\n", + " self.original = mobject.copy()\n", + " super().__init__(mobject, **kwargs)\n", + "\n", + " def interpolate_mobject(self, alpha: float) -> None:\n", + " point = self.path.point_from_proportion(self.rate_func(alpha))\n", + "\n", + " # tohle není úplně čisté, jelikož pokaždé vytváříme nový objekt\n", + " # je to kvůli tomu, že obj.fade() nenastavuje průhlednost ale přidává jí\n", + " self.mobject.become(self.original.copy()).move_to(point).fade(alpha)\n", + "\n", + "\n", + "class BezierExample(Scene):\n", + " def construct(self):\n", + " # křivku definujeme přes čtyři body:\n", + " # 2 krajní, ve kterých začíná a končí\n", + " # 2 kontrolní, které určují tvar\n", + " positions = [\n", + " UP + LEFT * 3, # počáteční\n", + " UP + RIGHT * 2, # 1. kontrolní\n", + " DOWN + LEFT * 2, # 2. kontrolní\n", + " DOWN + RIGHT * 3, # koncový\n", + " ]\n", + "\n", + " points = VGroup(*[Dot().move_to(position) for position in positions]).scale(1.5)\n", + "\n", + " # rozlišíme kontrolní body\n", + " points[1].set_color(BLUE)\n", + " points[2].set_color(BLUE)\n", + "\n", + " bezier = CubicBezier(*positions).scale(1.5)\n", + "\n", + " self.play(Write(bezier), Write(points))\n", + "\n", + " # animace posunu\n", + " circle = Circle(fill_opacity=1, stroke_opacity=0).scale(0.25).move_to(points[0])\n", + "\n", + " self.play(FadeIn(circle, shift=RIGHT * 0.5))\n", + " self.play(MoveAlongPath(circle, bezier))\n", + "\n", + " self.play(FadeOut(circle))\n", + "\n", + " # animace posunu s mizením\n", + " circle = (\n", + " Circle(fill_color=GREEN, fill_opacity=1, stroke_opacity=0)\n", + " .scale(0.25)\n", + " .move_to(points[0])\n", + " )\n", + "\n", + " self.play(FadeIn(circle, shift=RIGHT * 0.5))\n", + " self.play(MoveAndFade(circle, bezier))\n", + "\n", + " self.play(FadeOut(bezier), FadeOut(points), FadeOut(circle))" + ] + }, + { + "cell_type": "markdown", + "id": "4de93509", + "metadata": {}, + "source": [ + "### 3D Game of Life [9b]" + ] + }, + { + "cell_type": "markdown", + "id": "eee60fd6", + "metadata": {}, + "source": [ + "#### Dvoustavový [5b]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d95d2328", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh GOLTwoState\n", + "\n", + "\n", + "class GOLTwoState(ThreeDScene):\n", + " def construct(self):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "3fd9c87c", + "metadata": {}, + "source": [ + "#### Vícestavový [3b]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9525cea2", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh GOLMultiState\n", + "\n", + "\n", + "class GOLMultiState(ThreeDScene):\n", + " def construct(self):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "464edd8b", + "metadata": {}, + "source": [ + "#### Vlastní [1b]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "01394a96", + "metadata": {}, + "outputs": [], + "source": [ + "%%manim -v WARNING -qh GOLCustom\n", + "\n", + "\n", + "class GOLCustom(ThreeDScene):\n", + " def construct(self):\n", + " pass" + ] + } + ], + "metadata": { + "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.10.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/serial5.ipynb b/serial5.ipynb new file mode 100644 index 0000000..ca00474 --- /dev/null +++ b/serial5.ipynb @@ -0,0 +1,508 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "substantial-impact", + "metadata": {}, + "source": [ + "# Vítej!" + ] + }, + { + "cell_type": "markdown", + "id": "first-armenia", + "metadata": {}, + "source": [ + "Tento dokument obsahuje zdrojové kódy animací k páté sérii seriálu KSP. Před spouštěním opět nezapomeň Manim importovat spuštěním následujícího řádku!" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e03b150c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Manim Community v0.15.0\n",
+       "\n",
+       "
\n" + ], + "text/plain": [ + "Manim Community \u001b[32mv0.\u001b[0m\u001b[32m15.0\u001b[0m\n", + "\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from manim import *" + ] + }, + { + "cell_type": "markdown", + "id": "5bd0ac2f", + "metadata": {}, + "source": [ + "## Vlastní animace" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "01394a96", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%manim -v WARNING -qh --disable_caching StarFox\n", + "# POZOR! Při změnách musíme opět zakázat cachování\n", + "# (Manim při změně animace nepozná změnu ve scéně)\n", + "\n", + "\n", + "\n", + "class Roll(Animation):\n", + " \"\"\"Animace, která otočí objekt o daný úhel, trochu ho při tom zmenší a posune se do strany.\"\"\"\n", + " \n", + " def __init__(self, mobject: Mobject, angle, direction, scale_ratio=0.85, **kwargs):\n", + " \"\"\"Konstruktor. Inicializuje potřebné věci animace.\"\"\"\n", + " # bude se nám hodit původní verze objektu, který animujeme\n", + " # (v animaci ho otiž budeme měnit)\n", + " self.original = mobject.copy()\n", + " \n", + " self.scale_ratio = scale_ratio\n", + " self.angle = angle\n", + " self.direction = direction\n", + " \n", + " super().__init__(mobject, **kwargs)\n", + "\n", + " def interpolate_mobject(self, alpha: float) -> None:\n", + " \"\"\"Funkce, která se volá každý snímek, aby se animace animovala.\"\"\"\n", + " \n", + " # alpha je od 0 do 1, ale animace mohla jako parametr dostat rate funkci\n", + " # proto je třeba tuto funkci na alphu aplikovat, a by se animace chovala správně\n", + " actual_alpha = self.rate_func(alpha)\n", + "\n", + " # chceme, aby objekt měl na začátku scale 1, v půlce scale_ratio a na konci 1\n", + " # tohle možná není nejelegantnější způsob, ale funguje\n", + " scale_alpha = 1 - (1 - self.scale_ratio) * 2 * (0.5 - abs(actual_alpha - 0.5))\n", + " \n", + " # chceme, aby objekt měl na začátku startovní pozici, pak se posunul a nakonec se vrátil\n", + " direction_alpha = there_and_back(actual_alpha)\n", + " \n", + " self.mobject.become(self.original.copy())\\\n", + " .rotate(actual_alpha * self.angle)\\\n", + " .scale(scale_alpha)\\\n", + " .shift(self.direction * direction_alpha)\n", + "\n", + " \n", + "class Dissolve(AnimationGroup):\n", + " \"\"\"Animace, která 'zmizí' objekt. Používáme zde AnimationGroup,\n", + " jelikož animace mizení je tvořena dvěma různými animacemi.\"\"\"\n", + " \n", + " def __init__(self, mobject: Mobject, **kwargs):\n", + " \"\"\"Konstruktor. Inicializuje potřebné věci animace.\"\"\"\n", + " self.original = mobject.copy()\n", + " \n", + " # způsob, jak do animate syntaxu dostaneme argumenty\n", + " a1 = mobject.animate.scale(0)\n", + " a2 = Flash(mobject, color=mobject.color)\n", + " \n", + " super().__init__(a1, a2, lag_ratio=0.75, **kwargs)\n", + " \n", + " \n", + "class StarFox(Scene):\n", + " def construct(self):\n", + " square = Square(color=BLUE, fill_opacity=0.75).scale(1.5)\n", + " \n", + " self.play(Roll(square, angle=PI, direction=LEFT * 0.75))\n", + " self.play(Roll(square, angle=-PI, direction=RIGHT * 0.75))\n", + " \n", + " self.play(Dissolve(square))" + ] + }, + { + "cell_type": "markdown", + "id": "cb6584c4", + "metadata": {}, + "source": [ + "## Pluginy" + ] + }, + { + "cell_type": "markdown", + "id": "226fa759", + "metadata": {}, + "source": [ + "### Fyzika" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "49f08ea5", + "metadata": {}, + "outputs": [], + "source": [ + "from manim_physics import *" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "18742bd7", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%manim -v WARNING -qh FallingObjectsExample\n", + "\n", + "\n", + "# příklad z https://github.com/Matheart/manim-physics\n", + "# SpaceScene je třída podporující fyzikální interakce\n", + "class FallingObjectsExample(SpaceScene):\n", + " def construct(self):\n", + " circle = Circle().shift(UP)\n", + " circle.set_fill(RED, 1)\n", + " circle.shift(DOWN + RIGHT)\n", + "\n", + " rect = Square().shift(UP)\n", + " rect.rotate(PI / 4)\n", + " rect.set_fill(YELLOW_A, 1)\n", + " rect.shift(UP * 2)\n", + " rect.scale(0.5)\n", + "\n", + " ground = Line([-4, -3.5, 0], [4, -3.5, 0])\n", + " wall1 = Line([-4, -3.5, 0], [-4, 3.5, 0])\n", + " wall2 = Line([4, -3.5, 0], [4, 3.5, 0])\n", + " walls = VGroup(ground, wall1, wall2)\n", + " self.add(walls)\n", + "\n", + " self.play(\n", + " DrawBorderThenFill(circle),\n", + " DrawBorderThenFill(rect),\n", + " )\n", + " \n", + " # až doposud se jednalo o normální Manim kód\n", + " # nyní použijeme funkce, které objektům přidají fyziku\n", + " self.make_rigid_body(rect, circle) # čtverec a kruh jsou rigidní (hýbou se)\n", + " self.make_static_body(walls) # zdi jsou statické (nehýbou jse)\n", + " \n", + " # nyní počkáme - funkce výše přidaly objektům updatery\n", + " self.wait(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "61d9d3aa", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%manim -v WARNING -qh ElectricFieldExample\n", + "\n", + "\n", + "# zde stačí Scene, protože používáme pouze nové objekty\n", + "# příklad upravený z https://github.com/Matheart/manim-physics\n", + "# POZOR: kód trvá postavit opravdu dlouho, doporučuji používat nižší kvalitu\n", + "class ElectricFieldExample(Scene):\n", + " def construct(self):\n", + " charge1 = Charge(-1, LEFT + DOWN)\n", + " charge2 = Charge(2, RIGHT + DOWN)\n", + " charge3 = Charge(-1, UP)\n", + " \n", + " def rebuild(field):\n", + " \"\"\"Funkce která přestaví elektrické pole.\"\"\"\n", + " field.become(ElectricField(charge1, charge2, charge3))\n", + " \n", + " field = ElectricField(charge1, charge2, charge3)\n", + " \n", + " self.add(field, charge1, charge2, charge3)\n", + " \n", + " self.play(Write(field), FadeIn(charge1), FadeIn(charge2), FadeIn(charge3))\n", + " \n", + " field.add_updater(rebuild)\n", + " \n", + " self.play(\n", + " charge1.animate.shift(LEFT),\n", + " charge2.animate.shift(RIGHT),\n", + " charge3.animate.shift(DOWN * 0.5),\n", + " run_time=2,\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "bba36496", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%manim -v WARNING -qh MagnetismExample\n", + "\n", + "\n", + "# příklad upravený z https://github.com/Matheart/manim-physics\n", + "# OPĚT POZOR: kód trvá postavit opravdu dlouho, doporučuji používat nižší kvalitu\n", + "class MagnetismExample(Scene):\n", + " def construct(self):\n", + " current1 = Current(LEFT * 2.5)\n", + " current2 = Current(RIGHT * 2.5, direction=IN)\n", + " \n", + " def rebuild(field):\n", + " \"\"\"Funkce která přestaví magnetické pole.\"\"\"\n", + " field.become(MagneticField(current1, current2))\n", + " \n", + " field = MagneticField(current1, current2)\n", + " \n", + " self.play(Write(field), FadeIn(current1), FadeIn(current2))\n", + " \n", + " field.add_updater(rebuild)\n", + " \n", + " self.play(\n", + " Rotate(current1, about_point=ORIGIN, angle=PI),\n", + " Rotate(current2, about_point=ORIGIN, angle=PI),\n", + " run_time=2,\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a63686ae", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%manim -v WARNING -qh Pendulum\n", + "\n", + "\n", + "# příklad upravený z https://github.com/Matheart/manim-physics\n", + "# opět používáme SpaceScene, jelikož animujeme fyzikální interakce\n", + "class Pendulum(SpaceScene):\n", + " def construct(self):\n", + " # pozice kuliček pendula\n", + " bob_positions = [RIGHT * 1.5 + UP, RIGHT * 1.5 + UP * 2]\n", + " \n", + " pendulum = MultiPendulum(\n", + " *bob_positions,\n", + " pivot_point=UP,\n", + " bob_style={\"color\": WHITE, \"fill_opacity\": 1, \"radius\": 0.15},\n", + " )\n", + " \n", + " self.make_rigid_body(pendulum.bobs) # kuličky pendula jsou rigidní\n", + " pendulum.start_swinging() # a spojené\n", + " \n", + " self.add(pendulum)\n", + " \n", + " # budeme sledovat cestu obou kuliček\n", + " for i, bob in enumerate(pendulum.bobs):\n", + " self.bring_to_back(TracedPath(bob.get_center, stroke_color=DARK_GRAY))\n", + " \n", + " self.wait(12)" + ] + }, + { + "cell_type": "markdown", + "id": "b1c1214e", + "metadata": {}, + "source": [ + "### Chemie" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "74825cc6", + "metadata": {}, + "outputs": [], + "source": [ + "from chanim import *" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "af492827", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%manim -v WARNING -qh ChanimExample\n", + "\n", + "\n", + "class ChanimExample(Scene):\n", + " def construct(self):\n", + " # chemická sloučenina\n", + " # interně využívá ChemFigový syntax (https://www.ctan.org/pkg/chemfig)\n", + " chem = ChemWithName(\n", + " \"*6((=O)-N(-CH_3)-*5(-N=-N(-CH_3)-=)--(=O)-N(-H_3C)-)\",\n", + " \"Caffeine\"\n", + " )\n", + " \n", + " chem.move_to(ORIGIN)\n", + " \n", + " self.play(chem.creation_anim())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b2c6e7b5", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "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.10.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}