{ "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 }