Zdrojové kódy k příkladům a úlohám 34. série KSP (Manim).
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

447 lines
16 KiB

2 years ago
{
"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
}